Skip to left side bar
>
  • File
  • Edit
  • View
  • Run
  • Kernel
  • Tabs
  • Settings
  • Help

Open Tabs

  • 01-python-getting-started.2022-12-23T07-21-48-609Z.ipynb
  • 02-python-advanced.ipynb
  • 03-pandas-getting-started.2022-12-23T07-21-48-609Z.ipynb
  • 04-pandas-advanced.2022-12-23T07-21-48-609Z.ipynb
  • 05-pandas-summary-statistics.2022-12-23T07-21-48-609Z.ipynb
  • 06-visualization-matplotlib.2022-12-23T07-21-48-609Z.ipynb

Kernels

  • 17-ts-core.ipynb
  • 031-data-wrangling-with-mongodb.ipynb
  • 04-pandas-advanced.ipynb
  • 18-ts-models.ipynb
  • 11-databases-mongodb.ipynb
  • 10-databases-sql.ipynb
  • 033-autoregressive-models.ipynb
  • 035-assignment.ipynb
  • 032-linear-regression-with-time-series-data.ipynb
  • 034-arma-models-and-hyperparameter-tuning.ipynb
  • 01-python-getting-started.2022-12-23T07-21-48-609Z.ipynb
  • 02-python-advanced.ipynb
  • 03-pandas-getting-started.2022-12-23T07-21-48-609Z.ipynb
  • 04-pandas-advanced.2022-12-23T07-21-48-609Z.ipynb
  • 05-pandas-summary-statistics.2022-12-23T07-21-48-609Z.ipynb
  • 06-visualization-matplotlib.2022-12-23T07-21-48-609Z.ipynb

Terminals

    //ds-curriculum/@textbook/
    Name
    ...
    Last Modified
    • .ipynb_checkpoints35 minutes ago
    • data2 months ago
    • 01-python-getting-started.2022-12-23T07-21-48-609Z.ipynb37 minutes ago
    • 01-python-getting-started.ipynb2 months ago
    • 02-python-advanced.ipynb43 minutes ago
    • 03-pandas-getting-started.2022-12-23T07-21-48-609Z.ipynb2 months ago
    • 03-pandas-getting-started.ipynb2 months ago
    • 04-pandas-advanced.2022-12-23T07-21-48-609Z.ipynb33 minutes ago
    • 04-pandas-advanced.ipynb2 months ago
    • 05-pandas-summary-statistics.2022-12-23T07-21-48-609Z.ipynb32 minutes ago
    • 05-pandas-summary-statistics.ipynb2 months ago
    • 06-visualization-matplotlib.2022-12-23T07-21-48-609Z.ipynb11 minutes ago
    • 06-visualization-matplotlib.ipynb2 months ago
    • 07-visualization-pandas.ipynb2 months ago
    • 08-visualization-plotly.ipynba month ago
    • 09-visualization-seaborn.ipynba month ago
    • 10-databases-sql.ipynb2 months ago
    • 11-databases-mongodb.ipynb19 days ago
    • 12-ml-core.ipynb2 months ago
    • 13-ml-data-pre-processing-and-production.ipynba month ago
    • 14-ml-classification.ipynb2 months ago
    • 15-ml-regression.ipynba month ago
    • 16-ml-unsupervised-learning.ipynb2 months ago
    • 17-ts-core.ipynb2 months ago
    • 18-ts-models.ipynb2 months ago
    • 19-linux-command-line.ipynb2 months ago
    • 20-statistics.ipynb2 months ago
    • 21-python-object-oriented-programming.ipynb2 months ago
    • 22-apis.ipynb2 months ago
    • main.py3 months ago
    • 01-python-getting-started.2022-12-23T07-21-48-609Z.ipynb
    • 02-python-advanced.ipynb
    • 03-pandas-getting-started.2022-12-23T07-21-48-609Z.ipynb
    • 04-pandas-advanced.2022-12-23T07-21-48-609Z.ipynb
    • 05-pandas-summary-statistics.2022-12-23T07-21-48-609Z.ipynb
    • 06-visualization-matplotlib.2022-12-23T07-21-48-609Z.ipynb
    xxxxxxxxxx

    Python: Getting Started

    xxxxxxxxxx

    Simple Calculations¶

    xxxxxxxxxx

    In addition to all the more complex things you can do in Python, it is also useful for completing simple mathematical operations.

    xxxxxxxxxx

    Addition and Subtraction

    Add numbers together like this:

    [1]:
    xxxxxxxxxx
     
    1 + 1
    [1]:
    2
    xxxxxxxxxx

    Notice that you don't need to include = in order to make the operation work.

    Subtract numbers like this:

    [2]:
     
    2 - 1
    [2]:
    1
    xxxxxxxxxx

    Division

    Divide numbers like this:

    [3]:
     
    4 / 2
    [3]:
    2.0
    xxxxxxxxxx

    Remember that Python will return an error if you try to divide by 0!

    xxxxxxxxxx

    Modulo Division

    Perform modulo division like this:

    [4]:
     
    5 % 2
    [4]:
    1
    xxxxxxxxxx

    Multiplication

    Multiply numbers like this:

    [5]:
     
    4 * 2
    [5]:
    8
    xxxxxxxxxx

    Add exponents to numbers like this:

    [6]:
     
    4**2
    [6]:
    16
    xxxxxxxxxx

    Order of Operations¶

    Just like everywhere else, Python works on a specific order of operations. That order is:

    Parentheses Exponents Multiplication Addition Subtraction

    If you remember being taught PEMDAS, then this is the same thing! All the operations in Python work with levels of parentheses, so if you wanted to add two numbers together, and then add another number to the result, the code would look like this:

    [7]:
     
    (6 + 4) + 4
    [7]:
    14
    xxxxxxxxxx

    Things get more complicated when we add more terms to the equation, but the principle remains the same: if you open a set of parentheses, make sure you close it too. Here's an example that uses all the PEMDAS possibilities:

    [8]:
     
    (((5**3 + 7) * 4) / 16 + 9 - 2)
    [8]:
    40.0
    xxxxxxxxxx

    Data Types¶

    xxxxxxxxxx

    There are lots of different types of data in the world, and Python groups that data into several categories.

    Boolean (bool):

    • Any data which can be expressed as either True or False.
    • Used when comparing two values. For example, if you enter 10 > 9, Python will return True.

    String (str):

    • Data that involves text — either letters, numbers, or special characters.
    • Strings are enclosed in either single- or double-quotation marks: "string-1" or 'string-2'.

    Numeric (int, float, complex):

    • Data that can be expressed numerically.
    • An integer, or int, is a whole number, positive or negative, without decimals, of unlimited length: 123.
    • A floating-point number, or float, is a number, positive or negative, containing one or more decimals: 123.01.
    • A complex number, or complex, are imaginary numbers, designated by a j: (3 + 6j).

    Sequence (list, tuple, set):

    • Data that is a collection of discrete items.
    • A list is collection that is ordered and changeable. It's designated using square brackets [], and items can be of different data types: ["red", 1, 1.03, 1].
    • A tuple is a collection which is ordered and unchangeable. It's designated using parentheses (): ("red", 1, 1.03, 1).
    • A set is a collection which is unordered, unchangeable, and does not permit duplicate items. It's designated using curly brackets {}: {"red", 1, 1.03}.

    Mapping (dict):

    • Dictionaries store data in key-value pairs. They're designated using curly brackets {}, like a set, but notice that keys and values are associated with each other using a colon :. Each pair is separated from the next using a comma ,.
    dict1 = {
        "department": "quindio", 
        "property_type": "house", 
        "price_usd": 330899.98
    }
    

    Binary (bytes, bytearray, memoryview):

    • Used to manipulate and display binary data. That is, data that can be expressed with integers represented with base 2.
    • Unlike the other data types described above, binary types are not human-readable.
    xxxxxxxxxx

    Lists¶

    xxxxxxxxxx

    In Python, a list is a collection of data that stores multiple items in a single variable. These items must be ordered, able to be changed, and can be duplicated. A list can store data of multiple types; not all the items in the list need to be the same type.

    xxxxxxxxxx

    Creating Lists¶

    Lists can be as long or as short as you like. Let's create a short list based on data from the Colombian real estate market to give us something to work with.

    Lists are written with square brackets. Code for a short list that shows the price of houses in US dollars looks like this:

    [9]:
     
    price_usd = [97919.38, 300511.20, 293758.14]
    print(price_usd)
    [97919.38, 300511.2, 293758.14]
    
    xxxxxxxxxx

    Working with Lists¶

    After you've created a list, you can access any item on the list by referring to the item's index number. Keep in mind that in Python, the first item in a list is always 0.

    Let's access the second item of our price_usd list.

    [10]:
     
    print(price_usd[1])
    300511.2
    
    xxxxxxxxxx

    Practice

    Try it yourself! Create and print a list that shows the area of the houses, called area_m2. Include the items 187.0, 82.0, and 235.0.

    [40]:
    xxxxxxxxxx
    area_m2 = [187.0, 82.0,235.0]
     
    area_m2 = [187.0, 82.0,235.0]
    area_m2
    [40]:
    [187.0, 82.0, 235.0]
    xxxxxxxxxx

    If we want to access the an item at the end of the list, we can use negative indexing. In negative indexing, -1 refers to the last item, -2 to the second to last, and so on.

    Let's access the last item in our department list.

    [12]:
     
    print(price_usd[-1])
    293758.14
    
    xxxxxxxxxx

    Practice

    Try accessing the second item in your area_m2 list.

    [42]:
    x
     
    area_m2[1]
    [42]:
    82.0
    xxxxxxxxxx

    Try accessing the last item in the same list.

    [43]:
    xxxxxxxxxx
     
    area_m2[-1]
    [43]:
    235.0
    xxxxxxxxxx

    Appending Items¶

    It's also possible to add an item to a list that already exists using the append method like this:

    [44]:
    xxxxxxxxxx
    price_usd.append(540244.86)
     
    price_usd.append(540244.86)
    xxxxxxxxxx

    Practice

    Add the item 195.0 to your area_m2 list.

    [45]:
    xxxxxxxxxx
    area_m2.append(195.0)
     
    area_m2.append(195.0)
    print(area_m2)
    [187.0, 82.0, 235.0, 195.0]
    
    xxxxxxxxxx

    Aggregating Items¶

    We can also aggregate items on a list to make analyzing the list more useful. For example, if we wanted to know the total value in US dollars of the houses on our price_usd list, we could use the sum method.

    [46]:
     
    total_usd = sum(price_usd)
    xxxxxxxxxx

    We might also be interested in the average value in US dollars of the houses on the same list. To find the average, we add the len method to the sum method.

    [47]:
    xxxxxxxxxx
    average_usd = sum(price_usd) / len(price_usd)
     
    average_usd = sum(price_usd) / len(price_usd)
    xxxxxxxxxx

    Practice

    Try it yourself! Calculate the total area of the houses on your area_m2 list, and find the average area of all the houses on the list.

    [48]:
    xxxxxxxxxx
    print(total_area_m2)
     
    total_area_m2 = sum(area_m2)
    print(total_area_m2)
    699.0
    
    [49]:
    xxxxxxxxxx
    average_area_m2 = sum(area_m2)/len(area_m2)
     
    average_area_m2 = sum(area_m2)/len(area_m2)
    print(average_area_m2)
    174.75
    
    xxxxxxxxxx

    Zipping Items¶

    Finally, it might be useful to combine -- or zip -- two lists together. For example, we might want to create a new list that pairs the values in our price_usd list with our area_m2 list. To do that, we use the zip method. The code looks like this:

    You might have noticed that the above code involving putting one list (in this case, new_list) inside another list (in this case, list). This approach is called a generator, and we'll come back to what that is and how it works later in the course.

    [50]:
    xxxxxxxxxx
    new_list = zip(price_usd, area_m2)
     
    new_list = zip(price_usd, area_m2)
    zipped_list = list(new_list)
    zipped_list
    [50]:
    [(97919.38, 187.0), (300511.2, 82.0), (293758.14, 235.0), (540244.86, 195.0)]
    xxxxxxxxxx

    You might have noticed that the above code involving putting one list (in this case, new_list) inside another list (in this case, list). This approach is called a generator, and we'll come back to what that is and how it works later in the course.

    xxxxxxxxxx

    Practice

    Try it yourself! Create a list called area_m2 that includes the terms 235.0, 130.0, and 137.0, then create another list called price_cop that includes the terms 400000000.0, 850000000.0, and 475000000.0. Then zip them together to create a new list called area_price, and print the result.

    [20]:
     
    area_m2 = [235.0, 130.0, 137.0]
    price_cop = [400000000.0, 850000000.0,475000000.0]
    new_list2 = zip(area_m2,price_cop)
    zippedlist = list(new_list2)
    zippedlist
    [20]:
    [(235.0, 400000000.0), (130.0, 850000000.0), (137.0, 475000000.0)]
    xxxxxxxxxx

    Python for Loops¶

    xxxxxxxxxx

    A for Loop is used for executing a set of statements for each item in a list.

    xxxxxxxxxx

    Working with for Loops¶

    There can be as many statements as there are items in the list, but to keep things manageable, let's use our list of real estate values in Colombia.

    [21]:
     
    price_usd = [97919.38, 300511.20, 293758.14, 540244.86]
    print(price_usd)
    [97919.38, 300511.2, 293758.14, 540244.86]
    
    xxxxxxxxxx

    We might want to see each of the values in the list, so we insert a for Loop:

    [22]:
    xxxxxxxxxx
    price_usd = [97919.38, 300511.20, 293758.14, 540244.86]
     
    price_usd = [97919.38, 300511.20, 293758.14, 540244.86]
    for x in price_usd:
        print(x)
    97919.38
    300511.2
    293758.14
    540244.86
    
    xxxxxxxxxx

    Note that the print command is indented.

    xxxxxxxxxx

    Practice

    Try it yourself using the area_m2 list:

    [23]:
     
    area_m2 = ...
    [ ]:
     
    ​
    ​
    xxxxxxxxxx

    Python Dictionaries¶

    xxxxxxxxxx

    In Python, a dictionary is a collection of data that occurs in an order, is able to be changed, and does not allow duplicates. Data in a dictionary are always presented as keys and values, and those key-value pairs cannot be duplicated in the dataset.

    xxxxxxxxxx

    Creating Dictionaries¶

    Dictionaries can be as big or as small as you like. Let's create a small dictionary based on data from the Colombian real estate market to give us something to work with.

    Dictionaries are written with curly brackets, with key-value pairs inside. Code for a small dictionary looks like this:

    [24]:
     
    colomdict = {
        "property_type": "house",
        "department": "quindio",
        "area": 235.0,
    }
    colomdict
    [24]:
    {'property_type': 'house', 'department': 'quindio', 'area': 235.0}
    xxxxxxxxxx

    Practice

    Try it yourself! Create and print a dictionary called bogota with the key-value pairs "price_usd": 121,555.09, "area_m2": 82.0, and "property_type": "house"

    [25]:
     
    bogota = ...
    print(bogota)
    Ellipsis
    
    xxxxxxxxxx

    Working with Dictionaries¶

    After you've created a dictionary, you can access any item by using its key name inside square brackets.

    Going back to our example dictionary, let's access the value for "department".

    [26]:
     
    x = colomdict["department"]
    print(x)
    quindio
    
    xxxxxxxxxx

    Practice

    Try accessing the value for price_usd in the Bogotá dictionary you created above.

    [27]:
     
    x = ...
    ​
    xxxxxxxxxx

    You can also use get to retrieve a value. That looks like this:

    [28]:
     
    x = colomdict.get("department")
    xxxxxxxxxx

    Practice

    Now try accessing the value for area_m2 using the get method, and print the result.

    [29]:
     
    x = colomdict.get("area_m2")
    x
    ​
    xxxxxxxxxx

    Dictionary Keys¶

    xxxxxxxxxx

    Sometimes you want to know what the keys are in the dictionary, or you want to iterate through the dictionary. In such cases, you need to write code to access all keys in a dictionary by utilizing the keys method:

    [30]:
     
    colomdict.keys()
    [30]:
    dict_keys(['property_type', 'department', 'area'])
    xxxxxxxxxx

    If you need to use the keys in a list, you can transform the output:

    [31]:
     
    list(colomdict.keys())
    [31]:
    ['property_type', 'department', 'area']
    xxxxxxxxxx

    You can also iterate though colomdict.keys() without converting it to a list:

    [32]:
     
    for k in list(colomdict.keys()):
        print(k)
    property_type
    department
    area
    
    xxxxxxxxxx

    Practice

    print the value of each key in the colomdict dictionary

    [ ]:
     
    ​
    xxxxxxxxxx

    JSON¶

    xxxxxxxxxx

    JSON stands for Java Script Object Notation, and it's a text format for storing and transporting data.

    xxxxxxxxxx

    Working with JSON¶

    JSON works by creating key-value pairs, where the key is data that can be represented by letters (called a string). JSON values can be strings, numbers, objects, arrays, boolean data, or null. JSON usually comes as a list of dictionaries, which look like this:

    Here's an example from our colombia-real-estate-1 dataset with two key-value pairs that both include string values:

    [33]:
     
    [
        {"property_type": "house", "department": "bogota"},
        {"property_type": "house", "department": "bogota"},
        {"property_type": "house", "department": "bogota"},
    ]
    [33]:
    [{'property_type': 'house', 'department': 'bogota'},
     {'property_type': 'house', 'department': 'bogota'},
     {'property_type': 'house', 'department': 'bogota'}]
    xxxxxxxxxx

    Here's an simplified example from our colombia-real-estate-1 dataset with two key-value pairs that both include string values:

    [34]:
     
    {"property_type": "house", "department": "bogota"}
    [34]:
    {'property_type': 'house', 'department': 'bogota'}
    xxxxxxxxxx

    JSON pairs with numbers look like this:

    [35]:
     
    {"area_m2": 187.0, "price_usd": 330899.98}
    [35]:
    {'area_m2': 187.0, 'price_usd': 330899.98}
    xxxxxxxxxx

    When you mix more than one type of value, it looks like this:

    [36]:
     
    {"property_type": "house", "price_usd": 330899.98}
    [36]:
    {'property_type': 'house', 'price_usd': 330899.98}
    xxxxxxxxxx

    References & Further Reading¶

    • A guide to basic math operations in Python
    • Python documentation on built-in data types
    • Summary of Python data types
    • Tutorial on type conversion in Python
    • A description of how dictionaries work in Python
    • An introduction to JSON
    • An introduction to lists in Python
    • How to zip lists
    • Calculating mean, median, and mode in Python
    • A brief tutorial of For Loops
    xxxxxxxxxx

    Copyright © 2022 WorldQuant University. This content is licensed solely for personal use. Redistribution or publication of this material is strictly prohibited.

    xxxxxxxxxx

    Usage Guidelines

    This lesson is part of the DS Lab core curriculum. For that reason, this notebook can only be used on your WQU virtual machine.

    This means:

    • ⓧ No downloading this notebook.
    • ⓧ No re-sharing of this notebook with friends or colleagues.
    • ⓧ No downloading the embedded videos in this notebook.
    • ⓧ No re-sharing embedded videos with friends or colleagues.
    • ⓧ No adding this notebook to public or private repositories.
    • ⓧ No uploading this notebook (or screenshots of it) to other websites, including websites for study resources.

    xxxxxxxxxx

    Python: Advanced

    xxxxxxxxxx

    Strings¶

    xxxxxxxxxx

    What's a string? ¶

    Recall that a string is any kind of information that can be represented with letters.

    xxxxxxxxxx

    Working with strings ¶

    When working with data, often files and directories have names that fit a pattern. For example, data on property prices in Colombia and Mexico might be stored in files named:

    1. colombia-real-estate-1.csv
    2. colombia-real-estate-2.csv
    3. colombia-real-estate-3.csv
    4. mexico-city-real-estate-1.csv
    5. mexico-city-real-estate-2.csv
    6. mexico-city-real-estate-3.csv
    7. mexico-city-real-estate-4.csv
    8. mexico-city-real-estate-5.csv
    9. mexico-city-test-features.csv
    10. mexico-city-test-labels.csv

    When the list of files is short like this one, it's not difficult to find the ones we want, but if the list were longer, we might need some help. If we're only interested in finding files that deal with Mexico, we could search the files for files beginning with mexico-city-real-estate-. To do this, we'll use the .glob function. The code looks like this:

    [1]:
     
    import glob
    ​
    glob.glob("./data/mexico-city-real-estate-[0-9].csv")
    [1]:
    ['./data/mexico-city-real-estate-3.csv',
     './data/mexico-city-real-estate-1.csv',
     './data/mexico-city-real-estate-5.csv',
     './data/mexico-city-real-estate-4.csv',
     './data/mexico-city-real-estate-2.csv']
    xxxxxxxxxx

    The .glob function allows for pattern matching. In this example [0-9] allows for any digit between 0 and 9, but there are lots of other patterns that .glob can find. Here are a few of the more common ones:

    • * Match any number of characters
    • ? Match a single character of any kind
    • [a-z] Match any lower case alphabetical character in the current locale
    • [A-Z] Match any upper case alphabetical character in the current locale
    • [!a-z] Do not match any lower case alphabetical character in the current locale

    So, if we wanted to find all the files from Mexico City, we would use code like this:

    [2]:
     
    glob.glob("./data/mexico-city*")
    [2]:
    ['./data/mexico-city-real-estate-3.csv',
     './data/mexico-city-test-labels.csv',
     './data/mexico-city-real-estate-1.csv',
     './data/mexico-city-real-estate-5.csv',
     './data/mexico-city-real-estate-4.csv',
     './data/mexico-city-real-estate-2.csv',
     './data/mexico-city-test-features.csv']
    xxxxxxxxxx

    Practice

    Try it yourself! Find only the data files containing the word test.

    [ ]:
     
    ​
    xxxxxxxxxx

    So far, you have only searched for files in one specific directory. It's also possible to search for files in subdirectories. To get a listing of all notebook files starting from the directory above this one and all others below it, you can use:

    [3]:
     
    glob.glob("../**/*.ipynb", recursive=True)
    [3]:
    ['../050-bankruptcy-in-poland/056-data-dictionary.ipynb',
     '../050-bankruptcy-in-poland/054-gradient-boosting.ipynb',
     '../050-bankruptcy-in-poland/055-assignment.ipynb',
     '../050-bankruptcy-in-poland/051-working-with-json.ipynb',
     '../050-bankruptcy-in-poland/052-imbalanced-data.ipynb',
     '../050-bankruptcy-in-poland/053-random-forest.ipynb',
     '../030-air-quality-in-nairobi/035-assignment.ipynb',
     '../030-air-quality-in-nairobi/034-arma-models-and-hyperparameter-tuning.ipynb',
     '../030-air-quality-in-nairobi/031-data-wrangling-with-mongodb.ipynb',
     '../030-air-quality-in-nairobi/032-linear-regression-with-time-series-data.ipynb',
     '../030-air-quality-in-nairobi/033-autoregressive-models.ipynb',
     '../060-consumer-finances-in-usa/064-interactive-dash-app.ipynb',
     '../060-consumer-finances-in-usa/063-clustering-multiple-features.ipynb',
     '../060-consumer-finances-in-usa/065-assignment.ipynb',
     '../060-consumer-finances-in-usa/061-exploring-data.ipynb',
     '../060-consumer-finances-in-usa/066-data-dictionary.ipynb',
     '../060-consumer-finances-in-usa/062-clustering-two-features.ipynb',
     '../@textbook/13-ml-data-pre-processing-and-production.ipynb',
     '../@textbook/03-pandas-getting-started.2022-12-23T07-21-48-609Z.ipynb',
     '../@textbook/16-ml-unsupervised-learning.ipynb',
     '../@textbook/05-pandas-summary-statistics.2022-12-23T07-21-48-609Z.ipynb',
     '../@textbook/08-visualization-plotly.ipynb',
     '../@textbook/01-python-getting-started.2022-12-23T07-21-48-609Z.ipynb',
     '../@textbook/09-visualization-seaborn.ipynb',
     '../@textbook/17-ts-core.ipynb',
     '../@textbook/21-python-object-oriented-programming.ipynb',
     '../@textbook/15-ml-regression.ipynb',
     '../@textbook/10-databases-sql.ipynb',
     '../@textbook/20-statistics.ipynb',
     '../@textbook/01-python-getting-started.ipynb',
     '../@textbook/14-ml-classification.ipynb',
     '../@textbook/12-ml-core.ipynb',
     '../@textbook/22-apis.ipynb',
     '../@textbook/04-pandas-advanced.2022-12-23T07-21-48-609Z.ipynb',
     '../@textbook/05-pandas-summary-statistics.ipynb',
     '../@textbook/04-pandas-advanced.ipynb',
     '../@textbook/06-visualization-matplotlib.ipynb',
     '../@textbook/06-visualization-matplotlib.2022-12-23T07-21-48-609Z.ipynb',
     '../@textbook/02-python-advanced.ipynb',
     '../@textbook/19-linux-command-line.ipynb',
     '../@textbook/07-visualization-pandas.ipynb',
     '../@textbook/18-ts-models.ipynb',
     '../@textbook/03-pandas-getting-started.ipynb',
     '../@textbook/11-databases-mongodb.ipynb',
     '../070-ds-admissions-in-wqu/071-meet-ds-lab-applicants.ipynb',
     '../070-ds-admissions-in-wqu/072-etl-class.ipynb',
     '../070-ds-admissions-in-wqu/073-chi-square-test.ipynb',
     '../070-ds-admissions-in-wqu/075-assignment.ipynb',
     '../070-ds-admissions-in-wqu/074-dashboard.ipynb',
     '../080-volatility-forecasting-in-india/083-garch.ipynb',
     '../080-volatility-forecasting-in-india/081-working-with-apis.ipynb',
     '../080-volatility-forecasting-in-india/084-model-deployment.ipynb',
     '../080-volatility-forecasting-in-india/082-test-driven.ipynb',
     '../080-volatility-forecasting-in-india/085-assignment.ipynb',
     '../020-housing-in-buenos-aires/022-price-and-location.ipynb',
     '../020-housing-in-buenos-aires/021-price-and-size.ipynb',
     '../020-housing-in-buenos-aires/025-assignment.ipynb',
     '../020-housing-in-buenos-aires/023-price-and-neighborhood.ipynb',
     '../020-housing-in-buenos-aires/024-price-and-everything.ipynb',
     '../040-earthquake-damage-in-nepal/045-assignment.ipynb',
     '../040-earthquake-damage-in-nepal/046-data-dictionary.ipynb',
     '../040-earthquake-damage-in-nepal/042-logistic-regression.ipynb',
     '../040-earthquake-damage-in-nepal/044-demographics.ipynb',
     '../040-earthquake-damage-in-nepal/043-decision-tree.ipynb',
     '../040-earthquake-damage-in-nepal/041-sqlite.ipynb',
     '../010-housing-in-mexico/015-assignment.ipynb',
     '../010-housing-in-mexico/011-tabular-and-tidy-data.2022-12-23T07-21-48-609Z.ipynb',
     '../010-housing-in-mexico/012-data-wrangling-with-pandas.ipynb',
     '../010-housing-in-mexico/013-exploratory-data-analysis.2022-12-23T07-21-48-609Z.ipynb',
     '../010-housing-in-mexico/013-exploratory-data-analysis.ipynb',
     '../010-housing-in-mexico/012-data-wrangling-with-pandas.2022-12-23T07-21-48-609Z.ipynb',
     '../010-housing-in-mexico/011-tabular-and-tidy-data.ipynb',
     '../010-housing-in-mexico/014-size-or-location.ipynb']
    xxxxxxxxxx

    Manipulating Strings¶

    xxxxxxxxxx

    We can split a string on a specific character, transforming it into a list:

    [4]:
     
    file_name = "mexico-city-real-estate-1"
    file_name.split("-")
    [4]:
    ['mexico', 'city', 'real', 'estate', '1']
    xxxxxxxxxx

    This can be useful for a set of strings with similar naming conventions for us to access specific information. For example, we can collect the digit as the file number in the string:

    [5]:
     
    file_name_number = file_name.split("-")[-1]
    file_name_number
    [5]:
    '1'
    xxxxxxxxxx

    Another useful function to manipulate a string is replace. We can use it to replace any element in the string with another item. Note we put what we what to replace in the front and what we replace it with in the back:

    [6]:
     
    file_name = "mexico-city-real-estate-1"
    ​
    modified_file_name = file_name.replace("-", "_")
    ​
    modified_file_name
    [6]:
    'mexico_city_real_estate_1'
    xxxxxxxxxx

    Practice

    Change "mexico-city-real-estate-1" to "mexico-city-real-estate" using replace. Hint: To delete a character, we can replace it with an empty string ("").

    [7]:
     
    file_name = "mexico-city-real-estate-1"
    ​
    modified_file_name = ...
    ​
    modified_file_name
    [7]:
    Ellipsis
    xxxxxxxxxx

    Working with f-strings ¶

    xxxxxxxxxx

    We usually use print to examine output in Python, but most of the examples we've been printing have been relatively short. Formatted strings are helpful for all sorts of reasons, but when we're assembling and formatting a long string, using the print function can be difficult and time-consuming. Along the same lines, it's also useful to directly evaluate variables and expressions within strings. To do those things, we create f"" strings. The code looks like this:

    [8]:
     
    Home = "Mexico City"
    f"My home is {Home}"
    [8]:
    'My home is Mexico City'
    [9]:
     
    import datetime
    ​
    python_birthday = datetime.datetime(year=1991, month=2, day=20)
    print(
        f"Python first appeared on {python_birthday:%B %d} in the year {python_birthday:%Y}."
    )
    ​
    now = datetime.datetime.now()
    print(f"Python is {now.year - python_birthday.year} years old.")
    Python first appeared on February 20 in the year 1991.
    Python is 32 years old.
    
    xxxxxxxxxx

    Practice

    Mexico-Tenochtitlan was established on 13 March 1325; use f-strings to indicate how long ago that was.

    [20]:
    x
     
    mexico_founding = datetime.datetime(year=1325, month=3, day=13)
    now = datetime.datetime.now()
    ​
    f"Mexico-Tenochtitlan was established {now - mexico_founding} years ago."
    [20]:
    'Mexico-Tenochtitlan was established 254915 days, 8:49:47.247481 years ago.'
    xxxxxxxxxx

    Sources and further reading

    • Online tutorial on finding list lengths in Python
    • Official python documentation on the len function
    xxxxxxxxxx

    Iterators and Iterables¶

    xxxxxxxxxx

    A list is a container with a countable number of values. Because that's true, a list is an iterable, meaning that we can iterate through it one item at a time. In other words, iterators retrieve these values only when we ask for them. If we try to bring in a large database — over a million values, for example — asking for every action to be applied to every value will take up a huge amount of memory. Iterators are helpful because they allow us to free up memory to use for other tasks. We'll spend more time working with databases later on, but for now, let's take a look at some code:

    [ ]:
     
    from pymongo import MongoClient
    ​
    client = MongoClient(host="localhost", port=27017)
    ​
    (list(client.list_databases()))
    xxxxxxxxxx

    Setting aside the first two lines of code, we have a method which has returned a list of four databases. If we want to examine each database by itself, we can create a variable called results, and then try to print it.

    [ ]:
     
    results = client.list_databases()
    print((results))
    xxxxxxxxxx

    That doesn't seem like much of anything, but if we add the iterator next(), we'll get back something more useful.

    [ ]:
     
    print(next(results))
    xxxxxxxxxx

    That makes much more sense! As you can see, this returns the first row. If we do it again, we'll get the second row:

    [ ]:
     
    print(next(results))
    xxxxxxxxxx

    We can keep doing this until we get to the end of the list, at which point we'll get an error telling us that there's nothing left to print. Every time we use the next() method, we're using it as an iterator to iterate through our iterable.

    xxxxxxxxxx

    List Comprehension ¶

    xxxxxxxxxx

    List comprehension is used to iterate through lists without explicitly writing loops, which is especially useful for filtering data according to a specific condition.

    Let's take a look at a list that shows property prices in Mexican pesos.

    [ ]:
     
    price_mexican_pesos = [
        35000000.0,
        2000000.0,
        2700000.0,
        6347000.0,
        6994543.16,
        6617835.61,
        670000.0,
    ]
    xxxxxxxxxx

    But maybe we're interested in comparing these prices to property values in Colombia. To do that, we'll need to figure out how to express the data on our list in Colombian pesos. We can use a for loop to make the conversion based on an exchange rate of 1 Mexican peso to 190 Colombian pesos. The code looks like this:

    [ ]:
     
    price_colombian_pesos = []
    for price in price_mexican_pesos:
        price_colombian_pesos.append(price * 190)
    ​
    print(price_colombian_pesos)
    xxxxxxxxxx

    But what if we could do the same thing, but using fewer lines? That's what list comprehension is for. The code looks like this:

    [ ]:
     
    price_colombian_pesos = [price * 190 for price in price_mexican_pesos]
    ​
    print(price_colombian_pesos)
    xxxxxxxxxx

    We can use list comprehension to find all the house entries in this list of properties, like this:

    [ ]:
     
    records = [
        'sell,apartment,|México|Distrito Federal|Benito Juárez|,"19.384467,-99.135872",1860000.0,MXN,1843173.75,97996.85,,70.0,,26571.42857142857',
        'sell,apartment,|México|Distrito Federal|Iztapalapa|Cerro de La Estrella|,"19.324123,-99.074132",700000.0,MXN,693667.44,36880.53,,50.0,,14000.0',
        'sell,house,|México|Distrito Federal|La Magdalena Contreras|San Jerónimo Lídice|,"19.317653,-99.236291",3350000.0,MXN,3319694.98,176499.72,,350.0,,9571.42857142857',
        'sell,apartment,|México|Distrito Federal|Cuauhtémoc|,"19.446313,-99.14006",405108.0,MXN,401443.16,21343.71,,50.0,,8102.16',
        'sell,house,|México|Distrito Federal|Coyoacán|,"19.303906,-99.107812",7200000.0,MXN,7134866.79,379342.68,,250.0,,28800.0',
        'sell,apartment,|México|Distrito Federal|Benito Juárez|,"19.374171,-99.181264",2425000.0,MXN,2403062.73,127764.72,,96.0,,25260.416666666668',
        'sell,apartment,|México|Distrito Federal|Tlalpan|,"19.287428,-99.122283",1250000.0,MXN,1238692.07,65858.1,,65.0,,19230.76923076923',
        'sell,house,|México|Distrito Federal|Venustiano Carranza|,"19.436436,-99.117256",1362000.0,MXN,1349678.96,71758.99,,98.0,,13897.959183673467',
        'sell,apartment,|México|Distrito Federal|Benito Juárez|,"19.382429,-99.160199",2250000.0,MXN,2229645.73,118544.58,,90.0,,25000.0',
        'sell,house,|México|Distrito Federal|Tlalpan|Granjas Coapa|,"19.300456,-99.115741",3900000.0,MXN,3864719.42,205477.28,,153.0,,25490.19607843137',
        'sell,apartment,|México|Distrito Federal|Álvaro Obregón|,"19.363167,-99.276028",9000000.0,MXN,8918583.49,474178.35,,188.0,,47872.34042553192',
        'sell,house,|México|Distrito Federal|Coyoacán|Villa Coyoacán|,"19.348694,-99.16291",1150000.0,USD,21629775.0,1150000.0,,555.0,,2072.072072072072',
        'sell,house,|México|Distrito Federal|Tlalpan|,"19.300963,-99.144237",7500000.0,MXN,7432152.81,395148.62,,385.0,,19480.51948051948',
        'sell,house,|México|Distrito Federal|Coyoacán|Paseos de Taxqueña|,"19.343979,-99.124863",6310000.0,MXN,6252917.98,332451.71,,183.0,,34480.87431693989',
        'sell,apartment,|México|Distrito Federal|Coyoacán|San Diego Churubusco|,"19.354509,-99.149765",10000000.0,MXN,9909537.15,526864.83,,293.0,,34129.69283276451',
    ]
    [ ]:
     
    [row for row in records if "house" in row]
    xxxxxxxxxx

    Practice

    Explore the list records in the list, and find all entries located in Tlalpan

    [ ]:
     
    ​
    xxxxxxxxxx

    Functions¶

    xxxxxxxxxx

    When we code in Python, we want to create readable programs. One of the easiest ways to make a program readable is by not repeating sections of code that do the same thing. We do that by using functions. For example, you might have surface area of a property in square meters, but you want to see it in square feet. Keeping in mind that one square meter = 10.76391 square feet, you can write a function that starts with the area in square meters, and gives as output the area in square feet. The code looks like this:

    [ ]:
     
    def m2toft2(area_meter2):
        area_feet2 = 10.76391 * area_meter2
        return area_feet2
    xxxxxxxxxx

    The code above defines a function called m2toft2 that takes in a single input, called area_meters, and returns a single output, called area_feet. Let's try another one:

    [ ]:
     
    m2toft2(4)
    xxxxxxxxxx

    A function by itself can be difficult to understand, so let's add some comments describing what the function does.

    [ ]:
     
    def m2toft2(area_meter2):
        """
        This function takes in as input the area in meters squared
        and returns as an output the area in square feet
    ​
        input: area_meter2, the area in square meters
        output: area_feet2, the area in square feet
        """
        area_feet2 = 10.76391 * area_meter2
        return area_feet2
    xxxxxxxxxx

    This way, if you forget what m2toft2 does, Python will be able to remind you, like this:

    [ ]:
     
    help(m2toft2)
    xxxxxxxxxx

    This can be especially useful for large programs with lots of functions, some of which might have multiple arguments. For example, a function might take a list of areas of properties in square meters and a list of prices per square meter, and return lists with area in square feet and price per square foot. That function would look like this:

    [ ]:
     
    def convert_area(area_meters2):
        """
        This function takes in a list of area in square meters and
        returns area in square feet
    ​
        input: area_meters2, area in square meters
        output: area_feet, area in square feet
        """
        area_feet2 = [item * 10.76391 for item in area_meters2]
        return area_feet2
    xxxxxxxxxx

    Let's try it and see what happens:

    [ ]:
     
    surface_total_in_m2 = [1860000.0, 700000.0, 3350000.0]
    [ ]:
     
    surface_total_in_ft2 = convert_area(surface_total_in_m2)
    [ ]:
     
    print(surface_total_in_ft2)
    xxxxxxxxxx

    Practice

    Python comes with many predefined functions. Try this one:

    [ ]:
     
    help(max)
    xxxxxxxxxx

    Now write a function that returns the greatest per-unit-area property price for a list of property prices per-unit-area, and then use your function for the list price_usd_per_m2.

    [ ]:
     
    price_usd_per_m2 = [97996.85, 36880.53, 176499.72]
    [ ]:
     
    def find_max_price_per_area(price_per_meter2):
        """Find the most expensive price per unit areas given a list
    ​
        Parameters
        ----------
        price_per_meter : list of int
            List with price per unit area of each property
    ​
        Returns
        -------
        the price of the most expensive property per unit area
        """
        
        return ...
    ​
    ​
    find_max_price_per_area(price_usd_per_m2)
    xxxxxxxxxx

    Practice

    The previous example does not extend the max function that is in Python. Keeping in mind that list comprehensions can be used to iterate through lists, use list comprehension or loops to write a function which, given a list of property areas and a corresponding list of property prices per unit area, returns the total price of the most expensive property.

    [ ]:
     
    def find_max_price(area_meter2, price_per_meter2):
        """
        Given two lists, the first with areas of properties
        and the second with price per unit area, this function
        returns the most expensive property using list comprehension
    ​
        input: area_meter2, list with the total area of each property
        input: price_per_meter2, list with price per unit area of each property
        output: the price of the most expensive property
        """
        
        return ...
    ​
    ​
    find_max_price(surface_total_in_m2, price_usd_per_m2)
    xxxxxxxxxx

    Lambda Functions ¶

    xxxxxxxxxx

    The function definitions we've been working with so far are fine for most purposes, but they can easily become a little long. When that happens, you might want to use a shorter method to expressing a function; that's what lambda functions are for. Here's code for a function which adds 3 to a number.

    [ ]:
     
    add_three = lambda a: a + 3  # noqa: E731
    xxxxxxxxxx

    Now that we've defined our function, let's try it out. If we wanted the function to add 3 to 5, the code would look like this:

    [ ]:
     
    add_three(5)
    xxxxxxxxxx

    Practice

    Try it yourself! Write a lambda function called sub_4 which will subtract 4 from a given number, and then try it out with the number 7.

    [ ]:
     
    sub_4 = lambda a: a - 4  # noqa: E731
    ​
    xxxxxxxxxx

    Working with Errors¶

    xxxxxxxxxx

    Error Handling¶

    xxxxxxxxxx

    Error handling is a very important part of coding. It will make sure our code runs smoothly even with edge cases. try and except are the syntax we use in error handling. Let's create a function to demonstrate how this works.

    We start with a function that calculates the quotient of two numbers. There are two inputs of the function: nominator and denominator. The function works only when:

    • both inputs are numerical numbers
    • the denominator is not zero

    We can use try and except to make sure the function runs smoothly even with error inputs.

    [ ]:
     
    def get_quotient(nominator, denominator):
        try:
            quotient = nominator / denominator
            return quotient
        except:  # noQA E722
            return print("function not working")
    xxxxxxxxxx

    The function will return the quotient as long as there are no errors.

    [ ]:
     
    get_quotient(1, 2)
    xxxxxxxxxx

    The function will go the except section and print a message when inputs are wrong:

    [ ]:
     
    get_quotient(1, 0)
    xxxxxxxxxx

    Raising Errors¶

    xxxxxxxxxx

    Even though the previous function can handle the errors, it doesn't tell us what is the error causing the issue. In this case, we can print out the error message:

    [ ]:
     
    def get_quotient(nominator, denominator):
        try:
            quotient = nominator / denominator
            return quotient
        except Exception as e:
            return print(e)
    xxxxxxxxxx

    Now we can see the error message when denominator is zero:

    [ ]:
     
    get_quotient(1, 0)
    xxxxxxxxxx

    And the error message when inputs are not numerical:

    [ ]:
     
    get_quotient(1, "0")
    xxxxxxxxxx

    Practice

    Try it yourself! Rebuild the find_max_price_per_area function by adding error handling here, and make sure to raise errors when encounter one.

    [ ]:
     
    def find_max_price_per_area(price_per_meter2):
        """Find the most expensive price per unit areas given a list
    ​
        Parameters
        ----------
        price_per_meter : list of int
            List with price per unit area of each property
    ​
        Returns
        -------
        the price of the most expensive property per unit area
        """
    ​
        
    ​
    ​
    price_usd_per_m2 = [97996.85, 36880.53, 176499.72]
    find_max_price_per_area(price_usd_per_m2)
    [ ]:
     
    price_usd_per_m2 = ["97996.85", 36880.53, 176499.72]
    find_max_price_per_area(price_usd_per_m2)
    xxxxxxxxxx

    Files¶

    xxxxxxxxxx

    Create files using Context Manager¶

    A context manager allows you to allocate and release resources precisely when you want to. The most widely used example of context managers is the with statement. Suppose you have two related operations which you would like to execute as a pair, with a block of code in between. Context managers allow you to do specifically that. For example:

    [ ]:
     
    with open("data/example.txt", "w") as f:
        f.write("Hello")
    xxxxxxxxxx

    The code above will create a file called example.txt inside the data directory, with only one line: "Hello". We can add multiple lines to the file by adding the /n to separate the line.

    [ ]:
     
    with open("data/example.txt", "w") as f:
        f.write("Hello")
        f.write("\n")
        f.write("Hola")
    xxxxxxxxxx

    Practice

    Create a txt file named practice.txt inside the data directory with three lines using context manager.

    [ ]:
     
    ​
    xxxxxxxxxx

    Saving and Loading Files with joblib¶

    xxxxxxxxxx

    We can also use joblib's dump and load functions to save and load data. Besides saving and loading data, we can also save and load trained models for later use. Let's say an example here. First, we import joblib and train a model for the iris dataset:

    [ ]:
     
    from joblib import dump, load
    from sklearn import datasets, svm
    ​
    iris = datasets.load_iris()
    X, y = iris.data, iris.target
    ​
    clf = svm.SVC()
    clf.fit(X, y)
    [ ]:
     
    # Saving model to a path
    ​
    dump(clf, "data/trained_model.pkl")
    [ ]:
     
    # Load data from a path and make predictions again
    ​
    model = load("data/trained_model.pkl", mmap_mode="r")
    ​
    model.predict(X)
    xxxxxxxxxx

    Working with Filepaths¶

    xxxxxxxxxx

    A filepath is a directory to a specific file. Python uses the os module to work with path names and access files in the local directory. Here are some common use cases of the os module:

    xxxxxxxxxx

    os.getcwd() points to the current working directory:

    [ ]:
     
    import os
    ​
    print(f"Current working directory is at {os.getcwd()}")
    xxxxxxxxxx

    Let's see the following functions with an example. The path name shows the directory to a file called myfile.txt:WQU WorldQuant University Applied Data Science Lab QQQQ

    [ ]:
     
    pathname = "/home/jovyan/work/ds_curriculum/myfile.txt"
    xxxxxxxxxx

    First, os.path.abspath() returns a path name to the path passed as the parameter to this function.

    [ ]:
     
    os.path.abspath(pathname)
    xxxxxxxxxx

    If you only give a file name as the parameter, it will return a path with the current working directory and the file name:

    [ ]:
     
    filename = "myfile.txt"
    os.path.abspath(filename)
    xxxxxxxxxx

    os.path.dirname() will leave the file name part, and only show the directory of a path name.

    [ ]:
     
    os.path.dirname(pathname)
    xxxxxxxxxx

    On the other hand, os.path.basename() will only show the file name of a path name:

    [ ]:
     
    os.path.basename(pathname)
    xxxxxxxxxx

    The os.path.split() function splits a full path name and returns a tuple containing the path and filename. The first part of the tuple is the path to the file, the second part is the file:

    [ ]:
     
    os.path.split(pathname)
    xxxxxxxxxx

    The os.path.join() function constructs a path name out of one or more partial path names. Note the function will add an extra slash to the path name before joining it to the filename.

    [ ]:
     
    print(os.path.join("/new_directory", "myfile.txt"))
    xxxxxxxxxx

    Testing Code¶

    xxxxxxxxxx

    Python has a very useful function isinstance that can be used to check the type of an object. The following example checks whether the object is an integer.

    [11]:
    xxxxxxxxxx
     
    isinstance(1, int)
    [11]:
    True
    xxxxxxxxxx

    You can leverage the any and all function to apply conditions on multiple objects. For example, the following function checks whether any element of the inputs list is string:

    [12]:
     
    inputs = [1, 2]
    ​
    isinstance(any(inputs), str)
    [12]:
    False
    xxxxxxxxxx

    Since they are all integers, the function returns False. The following function checks whether all elements in the inputs list are either integers or floats:

    [14]:
    xxxxxxxxxx
     
    isinstance(all(inputs), (int, float))
    [14]:
    True
    xxxxxxxxxx

    You can add an assert function to your code to make sure you are using the right input data before you run the code:

    [15]:
    xxxxxxxxxx
     
    assert isinstance(1, int)
    xxxxxxxxxx

    If the statement after assert is True, the code will continue and nothing will be produced. If assert is False, then it will throw an error. You will use the error to debug your code.

    xxxxxxxxxx

    We can modify the get_quotient functioning by adding isinstance:

    [16]:
    xxxxxxxxxx
     
    def get_quotient(nominator, denominator):
        inputs = [nominator, denominator]
        if isinstance(all(inputs), (int, float)):
            quotient = nominator / denominator
            return quotient
        else:
            return print("denominator cannot be zero")
    xxxxxxxxxx

    Another function that is useful is hasattr, which checks what are the attributes for an object. The object can be a defined class, or a customized class. In the following example, I am checking whether the str class has the isupper and isstring attributes using hasattr. The function will return a boolean variable, either True or False:

    [17]:
    xxxxxxxxxx
     
    print("string has isupper method: ", hasattr(str, "isupper"))
    print("string has isstring method: ", hasattr(str, "isstring"))
    string has isupper method:  True
    string has isstring method:  False
    
    [18]:
    xxxxxxxxxx
     
    class Cat:
        age = 3
        name = "Lily"
    ​
    ​
    cat = Cat()
    ​
    hasattr(cat, "age")
    [18]:
    True
    [19]:
    xxxxxxxxxx
     
    hasattr(cat, "gender")
    [19]:
    False
    xxxxxxxxxx

    References and Further Reading¶

    xxxxxxxxxx
    • Context Manager
    xxxxxxxxxx

    Copyright 2022 WorldQuant University. This content is licensed solely for personal use. Redistribution or publication of this material is strictly prohibited.

    xxxxxxxxxx

    Pandas: Getting Started

    xxxxxxxxxx

    Pandas¶

    Pandas is a Python library used for working with datasets. It does that by helping us make sense of DataFrames, which are a form of two-dimensional structured data, like a table with columns and rows. But before we can do anything else, we need to start with data in a CSV file.

    xxxxxxxxxx

    Importing Data¶

    xxxxxxxxxx

    CSV Files¶

    CSV stands for Comma Separated Values, and it's a file type that allows data to be saved in a table. Data presented in a table is called structured data, because it adheres to the idea that there is a meaningful relationship between the columns and rows. A CSV might also show panel data, which is data that shows observations of the same behavior at various different times. The datasets we're using in this part of the course are all structured tables, but you'll see other arrangements of data as you move through your projects.

    If you're familiar with the way data tables look in spreadsheet applications like Excel, you might be surprised to see that raw CSV files don't look like that. If you came across a CSV file and opened it to see what it looked like, you'd see something like this:

    property_type,department,lat,lon,area_m2,price_usd
    house,Bogotá D.C,4.69,-74.048,187.0,"$330,899.98"
    house,Bogotá D.C,4.695,-74.082,82.0,"$121,555.09"
    house,Quindío,4.535,-75.676,235.0,"$219,474.47"
    house,Bogotá D.C,4.62,-74.129,195.0,"$97,919.38"
    
    xxxxxxxxxx

    Dictionaries¶

    xxxxxxxxxx

    You can create a DataFrame from a Python dictionary using from_dict function.

    [1]:
     
    import pandas as pd
    ​
    data = {"col_1": [3, 2, 1,0], "col_2": ["a", "b", "c", "d"]}
    pd.DataFrame.from_dict(data)
    [1]:
    col_1 col_2
    0 3 a
    1 2 b
    2 1 c
    3 0 d
    xxxxxxxxxx

    By default, DataFrame will be created using keys as columns. Note the length of the values should be equal for each key for the code to work. We can also let keys to be index instead of the columns:

    [2]:
     
    pd.DataFrame.from_dict(data, orient="index")
    [2]:
    0 1 2 3
    col_1 3 2 1 0
    col_2 a b c d
    xxxxxxxxxx

    We can also specify column names:

    [3]:
     
    pd.DataFrame.from_dict(data, orient="index", columns=["A", "B", "C", "D"])
    [3]:
    A B C D
    col_1 3 2 1 0
    col_2 a b c d
    xxxxxxxxxx

    Practice

    Try it yourself! Create a DataFrame called using the dictionary clothes and make the keys as index, and put column names as ['color','size']

    [4]:
     
    clothes = {"shirt": ["red", "M"], "sweater": ["yellow", "L"], "jacket": ["black", "L"]}
    ​
    pd.DataFrame.from_dict(clothes, orient="index", columns=["color","size"])
    [4]:
    color size
    shirt red M
    sweater yellow L
    jacket black L
    xxxxxxxxxx

    JSON Files¶

    xxxxxxxxxx

    JSON is short for JavaScript Object Notation. It is another widely used data format to store and transfer the data. It is light-weight and very human readable. In Python, we can use the json library to read JSON files. Here is an example of a JSON string.

    [5]:
     
    info = """{
        "firstName": "Jane",
        "lastName": "Doe",
        "hobby": "running",
        "age": 35
    }"""
    print(info)
    {
        "firstName": "Jane",
        "lastName": "Doe",
        "hobby": "running",
        "age": 35
    }
    
    xxxxxxxxxx

    Use json library to load the json string into a Python dictionary:

    [6]:
     
    import json
    ​
    data = json.loads(info)
    data
    [6]:
    {'firstName': 'Jane', 'lastName': 'Doe', 'hobby': 'running', 'age': 35}
    xxxxxxxxxx

    We can load a json string or file into a dictionary because they are organized in the same way: key-value pairs.

    [7]:
     
    data["firstName"]
    [7]:
    'Jane'
    xxxxxxxxxx

    A dictionary may not be as convenient as a DataFrame in terms of data manipulation and cleaning. But once we've turned our json string into a dictionary, we can transform it into a DataFrame using the from_dict method.

    [8]:
     
    df = pd.DataFrame.from_dict(data, orient="index", columns=["subject 1"])
    df
    [8]:
    subject 1
    firstName Jane
    lastName Doe
    hobby running
    age 35
    xxxxxxxxxx

    Practice

    Try it yourself! Load the JSON file clothes and then transform it to DataFrame, name column properly.

    [9]:
     
    clothes = """{"shirt": ["red","M"], "sweater": ["yellow","L"]}"""
    ​
    ​
    data = json.loads(clothes)
    df = pd.DataFrame.from_dict(data, orient="index", columns=["color","size"])
    df
    [9]:
    color size
    shirt red M
    sweater yellow L
    xxxxxxxxxx

    Load Compressed file in Python¶

    In the big data era, it is very likely that we'll need to read data from compressed files. One way to unzip the data is to use gzip. We can load the poland-bankruptcy-data-2008.json.gz file from the data folder using the following code:

    [10]:
     
    import gzip
    ​
    import json
    ​
    with gzip.open("data/poland-bankruptcy-data-2008.json.gz", "r") as f:
        poland_data_gz = json.load(f)
    xxxxxxxxxx

    poland_data_gz is a dictionary, and we only need the data portion of it.

    [11]:
     
    poland_data_gz.keys()
    [11]:
    dict_keys(['schema', 'data', 'metadata'])
    xxxxxxxxxx

    We can use the from_dict function from pandas to read the data:

    [12]:
     
    import pandas as pd
    df = pd.DataFrame().from_dict(poland_data_gz["data"])
    [13]:
     
    df.head()
    [13]:
    company_id feat_1 feat_2 feat_3 feat_4 feat_5 feat_6 feat_7 feat_8 feat_9 ... feat_56 feat_57 feat_58 feat_59 feat_60 feat_61 feat_62 feat_63 feat_64 bankrupt
    0 1 0.202350 0.46500 0.240380 1.5171 -14.547 0.510690 0.25366 0.91816 1.15190 ... 0.13184 0.473950 0.86816 0.00024 8.5487 5.16550 107.740 3.38790 5.3440 False
    1 2 0.030073 0.59563 0.186680 1.3382 -37.859 -0.000319 0.04167 0.67890 0.32356 ... 0.12146 0.074369 0.87235 0.00000 1.5264 0.63305 622.660 0.58619 1.2381 False
    2 3 0.257860 0.29949 0.665190 3.2211 71.799 0.000000 0.31877 2.33200 1.67620 ... 0.16499 0.369210 0.81614 0.00000 4.3325 3.19850 65.215 5.59690 47.4660 False
    3 4 0.227160 0.67850 0.042784 1.0828 -88.212 0.000000 0.28505 0.47384 1.32410 ... 0.29358 0.706570 0.78617 0.48456 5.2309 5.06750 142.460 2.56210 3.0066 False
    4 5 0.085443 0.38039 0.359230 1.9444 21.731 0.187900 0.10823 1.37140 1.11260 ... 0.10124 0.163790 0.89876 0.00000 5.7035 4.00200 89.058 4.09840 5.9874 False

    5 rows × 66 columns

    xxxxxxxxxx

    Practice

    Read poland-bankruptcy-data-2007.json.gz into a DataFrame.

    [14]:
     
    # Load file into dictionary
    import gzip
    import json
    import pandas as pd
    ​
    with gzip.open("data/poland-bankruptcy-data-2007.json.gz","r") as f:
         polandict = json.load(f)
    ​
    polandict.keys()
    # Transform dictionary into DataFrame
    df = pd.DataFrame.from_dict(polandict["data"])
    df.head()
    [14]:
    company_id feat_1 feat_2 feat_3 feat_4 feat_5 feat_6 feat_7 feat_8 feat_9 ... feat_56 feat_57 feat_58 feat_59 feat_60 feat_61 feat_62 feat_63 feat_64 bankrupt
    0 1 0.200550 0.37951 0.39641 2.0472 32.3510 0.38825 0.249760 1.33050 1.1389 ... 0.121960 0.39718 0.87804 0.001924 8.4160 5.1372 82.658 4.4158 7.4277 False
    1 2 0.209120 0.49988 0.47225 1.9447 14.7860 0.00000 0.258340 0.99601 1.6996 ... 0.121300 0.42002 0.85300 0.000000 4.1486 3.2732 107.350 3.4000 60.9870 False
    2 3 0.248660 0.69592 0.26713 1.5548 -1.1523 0.00000 0.309060 0.43695 1.3090 ... 0.241140 0.81774 0.76599 0.694840 4.9909 3.9510 134.270 2.7185 5.2078 False
    3 4 0.081483 0.30734 0.45879 2.4928 51.9520 0.14988 0.092704 1.86610 1.0571 ... 0.054015 0.14207 0.94598 0.000000 4.5746 3.6147 86.435 4.2228 5.5497 False
    4 5 0.187320 0.61323 0.22960 1.4063 -7.3128 0.18732 0.187320 0.63070 1.1559 ... 0.134850 0.48431 0.86515 0.124440 6.3985 4.3158 127.210 2.8692 7.8980 False

    5 rows × 66 columns

    xxxxxxxxxx

    Pickle Files¶

    xxxxxxxxxx

    Pickle in Python is primarily used in serializing and deserializing a Python object structure. Serialization is the process of turning an object in memory into a stream of bytes so you can store it on disk or send it over a network. Deserialization is the reverse process: turning a stream of bytes back into an object in memory.

    According to the pickle module documentation, the following types can be pickled:

    • None
    • Booleans
    • Integers, long integers, floating point numbers, complex numbers
    • Normal and Unicode strings
    • Tuples, lists, sets, and dictionaries containing only objects that can be pickled
    • Functions defined at the top level of a module
    • Built-in functions defined at the top level of a module
    • Classes that are defined at the top level of a module

    Let's demonstrate using a python dictionary as an example.

    [15]:
     
    clothes = {"shirt": ["red", "M"], "sweater": ["yellow", "L"], "jacket": ["black", "L"]}
    clothes
    [15]:
    {'shirt': ['red', 'M'], 'sweater': ['yellow', 'L'], 'jacket': ['black', 'L']}
    [16]:
     
    import pickle
    ​
    pickle.dump(clothes, open("./data/clothes.pkl", "wb"))
    xxxxxxxxxx

    Now in the data folder, there will be a file named clothes.pkl. We can read the pickled file using the following code:

    [17]:
     
    with open("./data/clothes.pkl", "rb") as f:
        unpickled = pickle.load(f)
    [18]:
     
    unpickled
    [18]:
    {'shirt': ['red', 'M'], 'sweater': ['yellow', 'L'], 'jacket': ['black', 'L']}
    xxxxxxxxxx

    Note first we are using wb inside the open function because we are creating this file, while deserializing the file, we are using rb to read the file

    xxxxxxxxxx

    Practice

    Store the sample list into a pickle file, and load the pickle file back to a list.

    [19]:
     
    sample_list = [1, 2, 3, 4, 5]
    [20]:
     
    import pickle
    pickle.dump(sample_list,open("./data/numlist.pkl","wb"))
    ​
    with open("./data/numlist.pkl","rb") as f:
        unpickled = pickle.load(f)
    unpickled
    [20]:
    [1, 2, 3, 4, 5]
    xxxxxxxxxx

    Working with DataFrames¶

    The first thing we need to do is import pandas; we'll use pd as an alias when we include it in our code.

    Pandas is just a library; to get anything done, we need a dataset too. We'll use the read_csv method to create a DataFrame from a CSV file.

    [21]:
     
    import pandas as pd
    ​
    df = pd.read_csv("data/colombia-real-estate-1.csv")
    df.head()
    [21]:
    property_type department lat lon area_m2 price_usd
    0 house Bogotá D.C 4.690 -74.048 187.0 $330,899.98
    1 house Bogotá D.C 4.695 -74.082 82.0 $121,555.09
    2 house Quindío 4.535 -75.676 235.0 $219,474.47
    3 house Bogotá D.C 4.620 -74.129 195.0 $97,919.38
    4 house Atlántico 11.012 -74.834 112.0 $115,477.34
    xxxxxxxxxx

    Practice

    Try it yourself! Create a DataFrame called df2 using the colombia-real-estate-2 CSV file.

    [22]:
     
    df2 = ...
    df2.head()
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    Cell In [22], line 2
          1 df2 = ...
    ----> 2 df2.head()
    
    AttributeError: 'ellipsis' object has no attribute 'head'
    xxxxxxxxxx

    Working with DataFrame Indices¶

    xxxxxxxxxx

    A DataFrame stores data in a row-and-column format. The DataFrame Index is a special kind of column that helps identify the location of each row. The default Index uses integers starting at zero, but you can also set up customized indices like "name", "location", etc. For example, in the following real estate data set, the default index are the integer counts.

    [1]:
     
    import pandas as pd
    ​
    df = pd.read_csv("./data/colombia-real-estate-2.csv")
    df.head()
    [1]:
    property_type department lat lon area_m2 price_cop
    0 house Magdalena 11.233 -74.204 235.0 4.000000e+08
    1 house Bogotá D.C 4.714 -74.030 130.0 8.500000e+08
    2 house Cundinamarca 4.851 -74.059 137.0 4.750000e+08
    3 house Atlántico 11.006 -74.808 346.0 1.400000e+09
    4 house Cundinamarca 4.857 -74.061 175.0 4.300000e+08
    xxxxxxxxxx

    We can call the index column through .index:

    [2]:
     
    df.index[:5]
    [2]:
    RangeIndex(start=0, stop=5, step=1)
    xxxxxxxxxx

    Use the set_index method, we can set the column department as the index instead. Note index column cannot have duplicate rows, like here we cannot set property_type as the index column.

    [ ]:
     
    df.set_index("department", inplace=True)
    df.head()
    xxxxxxxxxx

    Now you can see the index column has changed:

    [ ]:
     
    df.index[:5]
    xxxxxxxxxx

    Using the reset_index() function, we can reset index back to default integer counts, and department will become a column again.

    [ ]:
     
    df.reset_index(inplace=True)
    df.head()
    xxxxxxxxxx

    Practice

    Try it yourself! Set letter as the index, then call the index. Then reset the index.

    [ ]:
     
    data = {
        "letter": ["a", "b", "c", "d"],
        "number": [3, 2, 1, 0],
        "location": ["east", "east", "east", "west"],
    }
    df = pd.DataFrame.from_dict(data)
    ​
    # set index 'numbers'
    ​
    df.index[:3]
    df.set_index("letter",inplace=True)
    df.index[:3]
    [ ]:
     
    # reset index
    ​
    df
    xxxxxxxxxx

    Inspecting DataFrames¶

    Once we've created a DataFrame, we need to inspect it in order to see what's there. Pandas has many ways to inspect a DataFrame, but we're only going to look at three of them: shape, info, and head.

    If we're interested in understanding the dimensionality of the DataFrame, we can use the df.shape method. The code looks like this:

    [ ]:
     
    df.shape
    xxxxxxxxxx

    The shape output tells us that the colombia-real-estate-1 DataFrame -- which we called df1 -- has 3066 rows and 6 columns.

    xxxxxxxxxx

    If we're trying to get a general idea of what the DataFrame contained, we can use the info method. The code looks like this:

    [ ]:
     
    df.info()
    xxxxxxxxxx

    The info output tells us all sorts of things about the DataFrame: the number of columns, the names of the columns, the data type for each column, how many non-null rows are contained in the DataFrame.

    xxxxxxxxxx

    Practice

    Try it yourself! Use info and shape to explore df2, which you created above.

    [ ]:
     
    ​
    ​
    xxxxxxxxxx

    If we wanted to see all the rows in our new DataFrame, we could use the print method. Keep in mind that the entire dataset gets printed when you use print, even though it only shows you the first few lines. That's not much of a problem with this particular dataset, but once you start working with much bigger datasets, printing the whole thing will cause all sorts of problems.

    So instead of doing that, we'll just take a look at the first five rows by using the head method. The code looks like this:

    [ ]:
     
    df.head()
    xxxxxxxxxx

    By default, head returns the first five rows of data, but you can specify as many rows as you like. Here's what the code looks like for just the first two rows:

    [ ]:
     
    print(df.head(2))
    xxxxxxxxxx

    Practice

    Try it yourself! Use the head method to return the first five and first 7 rows of the colombia-real-estate-2 dataset.

    [ ]:
     
    ​
    ​
    xxxxxxxxxx

    Sorting¶

    xxxxxxxxxx

    Even though the DataFrame in many ways behaves similarly to a dict, it also is ordered. Therefore we can sort the data in it. Pandas provides two sorting methods, sort_values and sort_index.

    [2]:
     
    import pandas as pd
    ​
    df = pd.read_csv("data/colombia-real-estate-1.csv")
    df.head()
    [2]:
    property_type department lat lon area_m2 price_usd
    0 house Bogotá D.C 4.690 -74.048 187.0 $330,899.98
    1 house Bogotá D.C 4.695 -74.082 82.0 $121,555.09
    2 house Quindío 4.535 -75.676 235.0 $219,474.47
    3 house Bogotá D.C 4.620 -74.129 195.0 $97,919.38
    4 house Atlántico 11.012 -74.834 112.0 $115,477.34
    xxxxxxxxxx

    We can sort the whole DataFrame by values of a column.

    [2]:
     
    df.sort_values("area_m2").head()
    [2]:
    property_type department lat lon area_m2 price_usd
    958 house Cundinamarca 4.936000 -74.022000 64.0 $77,660.19
    1466 house Atlántico 10.999000 -74.803000 64.0 $104,672.44
    2883 house Bogotá D.C 4.618000 -74.069000 64.0 $110,750.19
    1947 apartment Bogotá D.C 4.695374 -74.063422 64.0 $117,600.67
    919 house Bogotá D.C 4.689000 -74.050000 64.0 $114,802.03
    xxxxxxxxxx

    We can also sort the DataFrame by it's index.

    [3]:
     
    df.set_index("lat").sort_index().head()
    [3]:
    property_type department lon area_m2 price_cop
    lat
    0.001298 apartment Cundinamarca 0.000463 140.0 8.200000e+08
    0.001392 apartment Cundinamarca 0.000139 124.0 2.300000e+08
    0.001655 apartment Cundinamarca -0.000050 120.0 5.900000e+08
    3.138000 house Valle del Cauca -76.593000 240.0 6.500000e+08
    3.179440 house Cundinamarca -75.638610 256.0 1.300000e+09
    xxxxxxxxxx

    Practice

    Sort the previous DataFrame by column price_usd, show head only.

    [ ]:
     
    ​
    xxxxxxxxxx

    Working with Columns¶

    Sometimes, it’s handy to duplicate a column of data. It might be that you’d like to drop some data points or erase empty cells while still preserving the original column. If you’d like to do that, you’ll need to duplicate the column. We can do this by placing the name of the new column in square brackets.

    xxxxxxxxxx

    Adding Columns¶

    For example, we might want to add a column of data that shows the price per square meter of each house in US dollars. To do that, we're going to need to create a new column, and include the necessary math to populate it. First, we need to import the CSV and inspect the first five rows using the head method, like this:

    [62]:
     
    df3 = pd.read_csv("data/colombia-real-estate-3.csv")
    df3.head(10)
    [62]:
    property_type place_with_parent_names lat-lon area_m2 price_usd
    0 house |Colombia|Bogotá D.C|Suba| 4.722,-74.059 113.0 162073.45
    1 house |Colombia|Valle del Cauca|Cali| 3.455,-76.522 210.0 151943.86
    2 house |Colombia|Bogotá D.C|Chapinero| 4.676,-74.044 183.0 422066.30
    3 house |Colombia|Atlántico|Barranquilla| 10.999,-74.816 85.0 84413.26
    4 house |Colombia|Valle del Cauca|Cali| 3.334,-76.547 145.0 131577.65
    5 house |Colombia|Bogotá D.C|Barrios Unidos| 4.7,-74.054 100.0 162073.45
    6 house |Colombia|Bogotá D.C|Suba| 4.725,-74.068 260.0 523362.21
    7 house |Colombia|Bogotá D.C|Suba| 4.729,-74.07 118.0 167138.25
    8 house |Colombia|Bogotá D.C|Suba| 4.726,-74.068 68.0 84413.26
    9 house |Colombia|Cundinamarca|La Mesa| 4.623,-74.471 67.0 93192.23
    xxxxxxxxxx

    Then, we create a new column called "price_m2", provide the formula to populate it, and inspect the first five rows of the dataset to make sure the new column includes the new values:

    [ ]:
     
    df3["price_m2"] = df3["price_usd"] / df3["area_m2"]
    df3.head()
    xxxxxxxxxx

    Practice

    Try it yourself! Add a column to the colombia-real-estate-2 dataset that shows the price per square meter of each house in Colombian pesos.

    [ ]:
     
    df = ...
    df["price_m2"] = ...
    ​
    xxxxxxxxxx

    Dropping Columns¶

    Just like we can add columns, we can also take them away. To do this, we’ll use the drop method. If I wanted to drop the “department” column from colombia-real-estate-1, the code would look like this:

    [ ]:
     
    df2 = df.drop("department", axis="columns")
    df2.head()
    xxxxxxxxxx

    Note that we specified that we wanted to drop a column by setting the axis argument to "columns". We can drop rows from the dataset if we change the axis argument to "index". If we wanted to drop row 2 from the df2 data, the code would look like this:

    [ ]:
     
    df2 = df.drop(2, axis="index")
    df2.head()
    xxxxxxxxxx

    Practice

    Try it yourself! Drop the "property_type" column and row 4 in the colombia-real-estate-2 dataset.

    [ ]:
     
    df1 = ...
    ​
    xxxxxxxxxx

    Dropping Rows¶

    Including rows with empty cells can radically skew the results of our analysis, so we often drop them from the dataset. We can do this with the dropna method. If we wanted to do this with df, the code would look like this:

    [ ]:
     
    print("df shape before dropping rows", df.shape)
    df.dropna(inplace=True)
    print("df shape after dropping rows", df.shape)
    df.head()
    xxxxxxxxxx

    By default, pandas will keep the original DataFrame, and will create a copy that reflects the changes we just made. That's perfectly fine, but if we want to make sure that copies of the DataFrame aren't clogging up the memory on our computers, then we need to intervene with the inplace argument. inplace=True means that we want the original DataFrame updated without making a copy. If we don't include inplace=True (or if we do include inplace=False), then pandas will revert to the default.

    xxxxxxxxxx

    Practice

    Drop rows with empty cells from the colombia-real-estate-2 dataset.

    [ ]:
     
    df2 = ...
    ​
    ​
    xxxxxxxxxx

    Splitting Strings¶

    It might be useful to split strings into their constituent parts, and create new columns to contain them. To do this, we’ll use the .str.split method, and include the character we want to use as the place where the data splits apart. In the colombia-real-estate-3 dataset, we might be interested breaking the "lat-lon" column into a "lat" column and a "lon" column. We’ll split it at “,” with code that looks like this:

    [60]:
     
    ​
    #df3["lat-lon"].str.split(",", expand=True)
    df3[["lat", "lon"]] = df3["lat-lon"].str.split(",", expand=True)
    [60]:
    0 1
    0 4.722 -74.059
    1 3.455 -76.522
    2 4.676 -74.044
    3 10.999 -74.816
    4 3.334 -76.547
    ... ... ...
    3060 4.728 -74.044
    3061 4.682 -74.056
    3062 3.346 -76.537
    3063 4.7 -74.028
    3064 4.718 -74.08

    2956 rows × 2 columns

    xxxxxxxxxx

    Here, expand is telling pandas to make the DataFrame bigger; that is, to create a new column without dropping any of the ones that already exist.

    xxxxxxxxxx

    Practice

    Try it yourself! In df3, split "place_with_parent_names" into three columns (one called "place", one called "department", and one called "state", using the character “|”, and then return the new "department" column.

    [59]:
     
    df3.dropna(inplace=True)
    df3[""]=df3["place_with_parent_names"].str.split("|",expand=True)[1]
    ​
    ​
    ​
    [59]:
    0       Colombia
    1       Colombia
    2       Colombia
    3       Colombia
    4       Colombia
              ...   
    3060    Colombia
    3061    Colombia
    3062    Colombia
    3063    Colombia
    3064    Colombia
    Name: 1, Length: 2956, dtype: object
    xxxxxxxxxx

    Recasting Data¶

    Depending on who formatted your dataset, the types of data assigned to each column might need to be changed. If, for example, a column containing only numbers had been mistaken for a column containing only strings, we’d need to change that through a process called recasting. Using the colombia-real-estate-1 dataset, we could recast the entire dataset as strings by using the astype method, like this:

    [ ]:
     
    print(df.info())
    newdf = df.astype("str")
    print(newdf.info())
    xxxxxxxxxx

    This is a useful approach, but, more often than not, you’ll want to only recast individual columns. In the colombia-real-estate-1 dataset, the "area_m2" column is cast as float64. Let's change it to int. We’ll still use the astype method, but we'll insert the name of the column. The code looks like this:

    [ ]:
     
    df["area_m2"] = df.area_m2.astype(int)
    df.info()
    xxxxxxxxxx

    Practice

    Try it yourself! In the colombia-real-estate-2 dataset, recast "price_cop" as an object.

    [ ]:
     
    df = ...
    df2["price_cop"] = ...
    df.info()
    xxxxxxxxxx

    Access a substring in a Series¶

    To access a substring from a Series, use the .str attribute from the Series. Then, index each string in the Series by providing the start:stop:step. Keep in mind that the start position is inclusive and the stop position is exclusive, meaning the value at the start index is included but the value at the stop index is not included. Also, Python is a 0-indexed language, so the first element in the substring is at index position 0. For example, using the colombia-real-estate-1 dataset, we could the values at index position 0, 2, and 4 of the department column:

    [3]:
     
    df["department"].str[0:5:2]
    [3]:
    0       Bgt
    1       Bgt
    2       Qid
    3       Bgt
    4       Aln
           ... 
    3061    Bgt
    3062    Blv
    3063    Cni
    3064    Bgt
    3065    Bgt
    Name: department, Length: 3066, dtype: object
    xxxxxxxxxx

    Practice: Access a substring in a Series using pandas

    Try it yourself! In the colombia-real-estate-2 dataset, access the property_type column and return the first 5 characters from each row:

    [ ]:
     
    ​
    xxxxxxxxxx

    Replacing String Characters¶

    Another change you might want to make is replacing the characters in a string. To do this, we’ll use the replace method again, being sure to specify which string should be replaced, and what new string should replace it. For example, if we wanted to replace the string “house” with the string “single_family” in the colombia-real-estate-1 dataset, the code would look like this:

    [ ]:
     
    df["property_type"] = df["property_type"].str.replace("house", "single_family")
    df.head()
    xxxxxxxxxx

    Note that the old value needs to come before the new value inside the parentheses of str.replace.

    xxxxxxxxxx

    Practice

    Try it yourself! In the colombia-real-estate-2 dataset, change “apartment” to “multi_family” and print the result.

    [ ]:
     
    df = ...
    ​
    xxxxxxxxxx

    Rename a Series¶

    Another change you might want to make is to rename a Series in pandas. To do this, we’ll use the rename method, being sure to specify the mapping of old and new columns. For example, if we wanted to replace the column name property_type with the string type_property in the colombia-real-estate-1 dataset, the code would look like this:

    [ ]:
     
    df.rename(columns={"property_type": "type_property"})
    xxxxxxxxxx

    Practice: Rename a Series

    Try it yourself! In the colombia-real-estate-2 dataset, change the column lat to latitude and print the head of DataFrame.

    [ ]:
     
    ​
    xxxxxxxxxx

    Determine the unique values in a column¶

    You might be interested in the unique values in a Series using pandas. To do this, we’ll use the unique method. For example, if we wanted to identify the unique values in the column property_type in the colombia-real-estate-1 dataset, the code would look like this:

    [4]:
     
    df["property_type"].unique()
    [4]:
    array(['house', 'apartment'], dtype=object)
    xxxxxxxxxx

    Practice: Determine the unique values in a column

    Try it yourself! In the colombia-real-estate-2 dataset, identify the unique values in the column department:

    [ ]:
     
    ​
    xxxxxxxxxx

    Replacing Column Values¶

    xxxxxxxxxx

    If you want to replace a columns' values, simply use the .replace() function:

    [5]:
     
    # Series.rename() example
    df = pd.read_csv("data/colombia-real-estate-2.csv")
    df.head()
    [5]:
    property_type department lat lon area_m2 price_cop
    0 house Magdalena 11.233 -74.204 235.0 4.000000e+08
    1 house Bogotá D.C 4.714 -74.030 130.0 8.500000e+08
    2 house Cundinamarca 4.851 -74.059 137.0 4.750000e+08
    3 house Atlántico 11.006 -74.808 346.0 1.400000e+09
    4 house Cundinamarca 4.857 -74.061 175.0 4.300000e+08
    xxxxxxxxxx

    We can replace a specific row with other values

    [ ]:
     
    df["area_m2"].replace(235.0, 0)
    xxxxxxxxxx

    If you want to replace multiple values at the same time, you can also define a dictionary ahead of time, with dictionary keys the originals and dictionary values the replaced values. Then pass the dictionary to the replace() function.

    [ ]:
     
    replace_value = {235: 0, 130: 1, 137: 2}
    ​
    df["area_m2"].replace(replace_value)
    xxxxxxxxxx

    Or we can apply specific operations to a whole column. In the following example, we have changed the price_cop unit to millions.

    [ ]:
     
    df["price_cop"] = df["price_cop"] / 1e6
    df.head()
    xxxxxxxxxx

    Practice: Replace Column Values

    Try it yourself! Define a dictionary to replace values in price_cop. Replace 400 to 0, 850 to 1.

    [ ]:
     
    replace_value = ...
    ​
    # Replace values
    ​
    xxxxxxxxxx

    Concatenating¶

    When we concatenate data, we're combining two or more separate sets of data into a single large dataset.

    xxxxxxxxxx

    Concatenating DataFrames¶

    If we want to combine two DataFrames, we need to import Pandas and read in our data.

    [3]:
     
    import pandas as pd
    df1 = pd.read_csv("data/colombia-real-estate-1.csv")
    df2 = pd.read_csv("data/colombia-real-estate-2.csv")
    print("df1 shape:", df1.shape)
    print("df2 shape:", df2.shape)
    df1 shape: (3066, 6)
    df2 shape: (3066, 6)
    
    xxxxxxxxxx

    Next, we'll use the concat method to put our DataFrames together, using each DataFrame's name in a list.

    [7]:
     
    concat_df = pd.concat([df1, df2])
    print("concat_df shape:", concat_df.shape)
    concat_df.head()
    concat_df shape: (6132, 7)
    
    [7]:
    property_type department lat lon area_m2 price_usd price_cop
    0 house Bogotá D.C 4.690 -74.048 187.0 $330,899.98 NaN
    1 house Bogotá D.C 4.695 -74.082 82.0 $121,555.09 NaN
    2 house Quindío 4.535 -75.676 235.0 $219,474.47 NaN
    3 house Bogotá D.C 4.620 -74.129 195.0 $97,919.38 NaN
    4 house Atlántico 11.012 -74.834 112.0 $115,477.34 NaN
    xxxxxxxxxx

    Practice

    Try it yourself! Create two DataFrames from colombia-real-estate-2.csv and colombia-real-estate-3.csv, and concatenate them as the DataFrame concat_df.

    [ ]:
     
    df2 = ...
    df3 = ...
    concat_df = ...
    concat_df.head()
    xxxxxxxxxx

    Concatenating Series¶

    We can also concatenate a Series using a similar set of commands. First, let's take two Series from the df1 and df2 respectively.

    [4]:
     
    df1 = pd.read_csv("data/colombia-real-estate-1.csv")
    df2 = pd.read_csv("data/colombia-real-estate-2.csv")
    sr1 = df1["property_type"]
    sr2 = df2["property_type"]
    print("len sr1:", len(sr1)),
    print(sr1.head())
    print()
    print("len sr2:", len(sr2)),
    print(sr2.head())
    len sr1: 3066
    0    house
    1    house
    2    house
    3    house
    4    house
    Name: property_type, dtype: object
    
    len sr2: 3066
    0    house
    1    house
    2    house
    3    house
    4    house
    Name: property_type, dtype: object
    
    xxxxxxxxxx

    Now that we have two Series, let's put them together.

    [5]:
     
    concat_sr = pd.concat([sr1, sr2])
    print("len concat_sr:", len(concat_sr)),
    print(concat_sr.head())
    len concat_sr: 6132
    0    house
    1    house
    2    house
    3    house
    4    house
    Name: property_type, dtype: object
    
    xxxxxxxxxx

    Practice

    Try it yourself! Use the colombia-real-estate-2 and colombia-rea-estate-3 datasets to create a concatenated Series for the area_m2 column, and print the result.

    [ ]:
     
    df1 = ...
    ​
    xxxxxxxxxx

    Saving a DataFrame as a CSV¶

    Once you’ve cleaned all your data and gotten the DataFrame to show everything you want it to show, it’s time to save the DataFrame as a new CSV file using the to_csv method. First, let's load up the colombia-real-estate-1 dataset, and use head to see the first five rows of data:

    [ ]:
     
    import pandas as pd
    ​
    df = pd.read_csv("data/colombia-real-estate-1.csv")
    df.head()
    xxxxxxxxxx

    Maybe we're only interested in those first five rows, so let's save that as its own new CSV file using the to_csv method. Note that we're setting the index argument to False so that the DataFrame index isn't included in the CSV file.

    [ ]:
     
    df = df.head()
    df.to_csv("data/small-df.csv", index=False)
    xxxxxxxxxx

    References & Further Reading¶

    • Tutorial for shape
    • Tutorial for info
    • Adding columns to a DataFrame
    • Creating DataFrame from dictionary
    • Working with JSON
    • Dropping columns from a DataFrame
    • Splitting columns in a DataFrame
    • Recasting values
    • Replacing strings
    • Concatenating DataFrames
    • From DataFrames to Series
    • Stack Overflow: What is serialization
    • Understand Python Pickling
    xxxxxxxxxx

    Copyright © 2022 WorldQuant University. This content is licensed solely for personal use. Redistribution or publication of this material is strictly prohibited.

    xxxxxxxxxx
    <font size="+3"><strong>Pandas: Advanced</strong></font>

    Pandas: Advanced

    xxxxxxxxxx
    # Calculate Summary Statistics for a DataFrame or Series

    Calculate Summary Statistics for a DataFrame or Series¶

    xxxxxxxxxx
    Many datasets are large, and it can be helpful to get a summary of information in them. Let's load a dataset and examine the first few rows:

    Many datasets are large, and it can be helpful to get a summary of information in them. Let's load a dataset and examine the first few rows:

    [1]:
     
    import pandas as pd
    ​
    mexico_city1 = pd.read_csv("./data/mexico-city-real-estate-1.csv")
    mexico_city1.head()
    [1]:
    operation property_type place_with_parent_names lat-lon price currency price_aprox_local_currency price_aprox_usd surface_total_in_m2 surface_covered_in_m2 price_usd_per_m2 price_per_m2 floor rooms expenses properati_url
    0 sell apartment |México|Distrito Federal|Álvaro Obregón| NaN 35000000.0 MXN 35634500.02 1894595.53 NaN NaN NaN NaN NaN NaN NaN http://alvaro-obregon.properati.com.mx/2eb_ven...
    1 sell apartment |México|Distrito Federal|Benito Juárez| NaN 2000000.0 MXN 2036257.11 108262.60 NaN NaN NaN NaN NaN NaN NaN http://benito-juarez.properati.com.mx/2ec_vent...
    2 sell apartment |México|Distrito Federal|Cuauhtémoc| 19.41501,-99.175174 2700000.0 MXN 2748947.10 146154.51 61.0 61.0 2395.975574 44262.295082 NaN 3.0 NaN http://cuauhtemoc.properati.com.mx/2pu_venta_a...
    3 sell apartment |México|Distrito Federal|Cuauhtémoc| 19.41501,-99.175174 6347000.0 MXN 6462061.92 343571.36 176.0 128.0 1952.110000 49585.937500 NaN 5.0 NaN http://cuauhtemoc.properati.com.mx/2pv_venta_a...
    4 sell apartment |México|Distrito Federal|Álvaro Obregón| NaN 6870000.0 MXN 6994543.16 371882.03 180.0 136.0 2066.011278 50514.705882 NaN 5.0 NaN http://alvaro-obregon.properati.com.mx/2pw_ven...
    xxxxxxxxxx
    Let's get a summary description of this dataset.

    Let's get a summary description of this dataset.

    [2]:
     
    mexico_city1.describe()
    [2]:
    price price_aprox_local_currency price_aprox_usd surface_total_in_m2 surface_covered_in_m2 price_usd_per_m2 price_per_m2 floor rooms expenses
    count 3.758000e+03 3.758000e+03 3.758000e+03 2850.000000 3662.000000 2075.000000 3561.000000 575.000000 202.000000 0.0
    mean 5.015710e+06 9.553774e+06 5.079498e+05 292.954386 250.817313 1906.810070 24475.917409 3.627826 3.039604 NaN
    std 7.355006e+06 1.544553e+07 8.211993e+05 1816.296990 336.555251 1813.415255 27430.941809 22.425889 1.410132 NaN
    min 1.485000e+05 1.471566e+05 7.823940e+03 0.000000 0.000000 1.034212 98.221416 1.000000 1.000000 NaN
    25% 1.068000e+06 1.585902e+06 8.431836e+04 0.000000 70.000000 669.510272 8684.210526 2.000000 2.000000 NaN
    50% 2.550000e+06 4.031164e+06 2.143267e+05 100.000000 146.500000 1415.132810 17460.317460 2.000000 3.000000 NaN
    75% 5.833201e+06 1.228003e+07 6.528979e+05 275.750000 310.000000 2498.628385 35810.810811 3.000000 4.000000 NaN
    max 1.250000e+08 3.364841e+08 1.789000e+07 84500.000000 8265.000000 28396.825397 750000.000000 450.000000 9.000000 NaN
    xxxxxxxxxx
    Like most large datasets, this one has many values which are missing. The describe function will ignore missing values in each column. You can also remove rows and columns with missing values, and then get a summary of the data that's still there. We need to remove columns first, before removing the rows; the sequence of operations here is important. The code looks like this:

    Like most large datasets, this one has many values which are missing. The describe function will ignore missing values in each column. You can also remove rows and columns with missing values, and then get a summary of the data that's still there. We need to remove columns first, before removing the rows; the sequence of operations here is important. The code looks like this:

    [3]:
     
    mexico_city1 = mexico_city1.drop(
        ["floor", "price_usd_per_m2", "expenses", "rooms"],axis =1)
    mexico_city1 = mexico_city1.dropna(axis=0)
    mexico_city1.head()
    [3]:
    operation property_type place_with_parent_names lat-lon price currency price_aprox_local_currency price_aprox_usd surface_total_in_m2 surface_covered_in_m2 price_per_m2 properati_url
    2 sell apartment |México|Distrito Federal|Cuauhtémoc| 19.41501,-99.175174 2700000.0 MXN 2748947.10 146154.51 61.0 61.0 44262.295082 http://cuauhtemoc.properati.com.mx/2pu_venta_a...
    3 sell apartment |México|Distrito Federal|Cuauhtémoc| 19.41501,-99.175174 6347000.0 MXN 6462061.92 343571.36 176.0 128.0 49585.937500 http://cuauhtemoc.properati.com.mx/2pv_venta_a...
    6 sell apartment |México|Distrito Federal|Miguel Hidalgo| 19.456564,-99.191724 670000.0 MXN 682146.11 36267.97 65.0 65.0 10307.692308 http://miguel-hidalgo-df.properati.com.mx/46h_...
    7 sell apartment |México|Distrito Federal|Gustavo A. Madero| 19.512787,-99.141393 1400000.0 MXN 1425379.97 75783.82 82.0 70.0 20000.000000 http://gustavo-a-madero.properati.com.mx/46p_v...
    8 sell house |México|Distrito Federal|Álvaro Obregón| 19.358776,-99.213557 6680000.0 MXN 6801098.67 361597.08 346.0 346.0 19306.358382 http://alvaro-obregon.properati.com.mx/46t_ven...
    xxxxxxxxxx
    Let's take a look at our new, cleaner dataset.

    Let's take a look at our new, cleaner dataset.

    [4]:
     
    mexico_city1.describe()
    [4]:
    price price_aprox_local_currency price_aprox_usd surface_total_in_m2 surface_covered_in_m2 price_per_m2
    count 1.930000e+03 1.930000e+03 1.930000e+03 1930.000000 1930.000000 1930.000000
    mean 5.090727e+06 9.808822e+06 5.215101e+05 311.264249 274.844560 23817.307978
    std 7.254611e+06 1.580064e+07 8.400795e+05 2170.418906 357.800175 26835.969068
    min 1.485000e+05 1.471566e+05 7.823940e+03 0.000000 2.000000 98.221416
    25% 1.064276e+06 1.615638e+06 8.589933e+04 0.000000 71.000000 8695.652174
    50% 2.700231e+06 3.915195e+06 2.081610e+05 90.000000 157.000000 17303.420882
    75% 5.906450e+06 1.228003e+07 6.528979e+05 280.000000 350.000000 35779.954128
    max 1.250000e+08 2.351062e+08 1.250000e+07 84500.000000 8265.000000 750000.000000
    xxxxxxxxxx
    <font size="+1">Practice</font> 

    Practice

    Reload the mexico-city-real-estate-1.csv dataset. Reverse the sequence of operations by first dropping all rows where there is a missing value, and then dropping the columns, floor, price_usd_per_m2,expenses and rooms. What is the size of the resulting DataFrame?

    [5]:
     
    mexico_city1 = pd.read_csv("./data/mexico-city-real-estate-1.csv")
    mexico_city1 = ...
    mexico_city1 = mexico_city1.drop(
        ["floor", "price_usd_per_m2", "expenses", "rooms"], axis=1
    )  # REMOVERHS
    print(mexico_city1.shape)
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    Cell In [5], line 3
          1 mexico_city1 = pd.read_csv("./data/mexico-city-real-estate-1.csv")
          2 mexico_city1 = ...
    ----> 3 mexico_city1 = mexico_city1.drop(
          4     ["floor", "price_usd_per_m2", "expenses", "rooms"], axis=1
          5 )  # REMOVERHS
          6 print(mexico_city1.shape)
    
    AttributeError: 'ellipsis' object has no attribute 'drop'
    xxxxxxxxxx
    # Select a Series from a DataFrame

    Select a Series from a DataFrame¶

    xxxxxxxxxx
    Since the datasets we work with are so large, you might want to focus on a single column of a DataFrame. Let's load up the `mexico-city-real-estate-2` dataset, and examine the first few rows to find the column names.

    Since the datasets we work with are so large, you might want to focus on a single column of a DataFrame. Let's load up the mexico-city-real-estate-2 dataset, and examine the first few rows to find the column names.

    [ ]:
     
    mexico_city2 = pd.read_csv("./data/mexico-city-real-estate-2.csv")
    mexico_city2.head()
    xxxxxxxxxx
    Maybe we're interested in the `surface_covered_in_m2` column. The code to extract just that one column looks like this:

    Maybe we're interested in the surface_covered_in_m2 column. The code to extract just that one column looks like this:

    [ ]:
     
    surface_covered_in_m2 = mexico_city2["surface_covered_in_m2"]
    surface_covered_in_m2
    xxxxxxxxxx
    <font size="+1">Practice</font> 

    Practice

    Select the price series from the mexico-city-real-estate-2 dataset, and load it into the mexico_city2 DataFrame

    [ ]:
     
    price = ...
    print(price)
    xxxxxxxxxx
    # Subset a DataFrame by Selecting One or More Columns

    Subset a DataFrame by Selecting One or More Columns¶

    xxxxxxxxxx
    You may find it more efficient to work with a smaller portion of a dataset that's relevant to you. One way to do this is to select some columns from a DataFrame and make a new DataFrame with them. Let's load a dataset to do this and examine the first few rows to find the column headings:

    You may find it more efficient to work with a smaller portion of a dataset that's relevant to you. One way to do this is to select some columns from a DataFrame and make a new DataFrame with them. Let's load a dataset to do this and examine the first few rows to find the column headings:

    [ ]:
     
    mexico_city4 = pd.read_csv("./data/mexico-city-real-estate-4.csv")
    mexico_city4.head()
    ​
    xxxxxxxxxx
    Let's choose `"operation"`, `"property_type"`, `"place_with_parent_names"`, and `"price"`:

    Let's choose "operation", "property_type", "place_with_parent_names", and "price":

    [ ]:
     
    mexico_city4_subset = mexico_city4[["operation", "property_type", "place_with_parent_names", "price"]]
    ​
    ​
    mexico_city4_subset
    xxxxxxxxxx
    Once we've done that, we can find the resulting number of entries in the DataFrame:

    Once we've done that, we can find the resulting number of entries in the DataFrame:

    [ ]:
     
    mexico_city4_subset.shape
    xxxxxxxxxx
    <font size="+1">Practice</font> 

    Practice

    Load the mexico-city-real-estate-1.csv dataset and subset it to obtain the operation, lat-lon and place_with_property_names columns only. How many entries are in the resulting DataFrame?

    [ ]:
     
    mexico_city1 = ...
    mexico_city1_subset = mexico_city1[
        ["operation", "lat-lon", "place_with_parent_names"]
    ]  # REMOVERHS
    print(mexico_city1_subset.shape)
    xxxxxxxxxx
    # Subset the Columns of a DataFrame Based on Data Types

    Subset the Columns of a DataFrame Based on Data Types¶

    xxxxxxxxxx
    It's helpful to be able to find specific types of entries — typically numeric ones — and put all of these in a separate DataFrame. First, let's take a look at the `mexico-city-real-estate-5` dataset.

    It's helpful to be able to find specific types of entries — typically numeric ones — and put all of these in a separate DataFrame. First, let's take a look at the mexico-city-real-estate-5 dataset.

    [ ]:
     
    mexico_city5 = pd.read_csv("./data/mexico-city-real-estate-5.csv")
    mexico_city5.head()
    xxxxxxxxxx
    The code to subset just the numerical entries looks like this:

    The code to subset just the numerical entries looks like this:

    [ ]:
     
    mexico_city5_numbers = mexico_city5.select_dtypes(include="number")
    mexico_city5_numbers.head()
    xxxxxxxxxx
    <font size="+1">Practice</font> 

    Practice

    Create a subset of the DataFrame from mexico-city-real-estate-5 which excludes numbers.

    [ ]:
     
    mexico_city3 = mexico_city5
    mexico_city3_no_numbers = mexico_city3.select_dtypes(include="object")
    print(mexico_city3_no_numbers.info())
    xxxxxxxxxx
    # Working with `value_counts` in a Series

    Working with value_counts in a Series¶

    In order to use the data in a series for other types of analysis, it might be helpful to know how often each value occurs in the Series. To do that, we use the value_counts method to aggregate the data. Let's take a look at the number of properties associated with each department in the colombia-real-estate-1 dataset.

    [ ]:
     
    df1 = pd.read_csv("data/colombia-real-estate-1.csv", usecols=["department"])
    ​
    #df1["department"].value_counts()
    df1.value_counts()
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Aggregate the different property types in the colombia-real-estate-2 dataset.

    [ ]:
     
    df2 = ...
    ​
    xxxxxxxxxx
    # Series and `Groupby`

    Series and Groupby¶

    Large Series often include data points that have some attribute in common, but which are nevertheless not grouped together in the dataset. Happily, pandas has a method that will bring these data points together into groups.

    Let's take a look at the colombia-real-estate-1 dataset. The set includes properties scattered across Colombia, so it might be useful to group properties from the same department together; to do this, we'll use the groupby method. The code looks like this:

    [ ]:
     
    dept_group = df1.groupby("department")
    df1
    xxxxxxxxxx
    To make sure we got all the departments in the dataset, let's print the first occurrence of each department.

    To make sure we got all the departments in the dataset, let's print the first occurrence of each department.

    [ ]:
     
    dept_group.first()
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Group the properties in colombia-real-estate-2 by department, and print the result.

    [ ]:
     
    df2 = pd.read_csv("data/colombia-real-estate-2.csv", usecols=["department"])
    dept_group = df2.groupby("department")
    dept_group.first()
    xxxxxxxxxx
    Now that we have all the properties grouped by department, we might want to see the properties in just one of the departments. We can use the `get_group` method to do that. If we just wanted to see the properties in `"Santander"`, for example, the code would look like this: 

    Now that we have all the properties grouped by department, we might want to see the properties in just one of the departments. We can use the get_group method to do that. If we just wanted to see the properties in "Santander", for example, the code would look like this:

    [ ]:
     
    dept_group1 = df1.groupby("department")
    dept_group1.get_group("Santander")
    xxxxxxxxxx
    We can also make groups based on more than one category by adding them to the `groupby` method. After resetting the `df1` DataFrame, here's what the code looks like if we want to group properties both by `department` and by `property_type`.

    We can also make groups based on more than one category by adding them to the groupby method. After resetting the df1 DataFrame, here's what the code looks like if we want to group properties both by department and by property_type.

    [ ]:
     
    df1 = pd.read_csv("data/colombia-real-estate-1.csv")
    dept_group2 = df1.groupby(["department", "property_type"])
    dept_group2.first()
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Group the properties in colombia-real-estate-2 by department and property type, and print the result.

    [ ]:
     
    dept_group = ...
    dept_group.first()
    xxxxxxxxxx
    Finally, it's possible to use `groupby` to calculate aggregations. For example, if we wanted to find the average property area in each department, we would use the `.mean()` method. This is what the code for that looks like:

    Finally, it's possible to use groupby to calculate aggregations. For example, if we wanted to find the average property area in each department, we would use the .mean() method. This is what the code for that looks like:

    [ ]:
     
    dept_group = df1.groupby("department")["area_m2"].mean()
    dept_group
    xxxxxxxxxx
    *Practice*

    Practice Try it yourself! Use the .mean method in the colombia-real-estate-2 dataset to find the average price in Colombian pesos ("price_cop") for properties in each "department".

    [ ]:
     
    dept_group = ...
    dept_group
    xxxxxxxxxx
    # Subset a DataFrame's Columns Based on the Column Data Types

    Subset a DataFrame's Columns Based on the Column Data Types¶

    xxxxxxxxxx
    It's helpful to be able to find entries of a certain type, typically numerical entries, and put all of these in a separate DataFrame. Let's load a dataset to see how this works:

    It's helpful to be able to find entries of a certain type, typically numerical entries, and put all of these in a separate DataFrame. Let's load a dataset to see how this works:

    [ ]:
     
    mexico_city5 = pd.read_csv("./data/mexico-city-real-estate-5.csv")
    mexico_city5.head()
    xxxxxxxxxx
    Now let's get only numerical entries:

    Now let's get only numerical entries:

    [ ]:
     
    mexico_city5_numbers = mexico_city5.select_dtypes(include="number")
    mexico_city5_numbers.head()
    xxxxxxxxxx
    Let's now find all entries which are not numerical entries:

    Let's now find all entries which are not numerical entries:

    [ ]:
     
    mexico_city5_no_numbers = mexico_city5.select_dtypes(exclude="number")
    mexico_city5_no_numbers.head()
    xxxxxxxxxx
    <font size="+1">Practice</font> 

    Practice

    Create a subset of the DataFrame from mexico-city-real-estate-5.csv which excludes numbers. How many entries does it have?

    [ ]:
     
    mexico_city3 = ...
    mexico_city3_no_numbers = ...
    print(mexico_city3_no_numbers.shape)
    xxxxxxxxxx
    # Subset a DataFrame's columns based on column names

    Subset a DataFrame's columns based on column names¶

    xxxxxxxxxx
    To subset a DataFrame by column names, either define a list of columns to include or define a list of columns to exclude. Once you've done that, you can retain or drop the columns accordingly. For example, let's suppose we want to modify the `mexico_city3` dataset and only retain the first three columns. Let's define two lists, one with the columns to retain and one with the columns to drop:

    To subset a DataFrame by column names, either define a list of columns to include or define a list of columns to exclude. Once you've done that, you can retain or drop the columns accordingly. For example, let's suppose we want to modify the mexico_city3 dataset and only retain the first three columns. Let's define two lists, one with the columns to retain and one with the columns to drop:

    [ ]:
     
    drop_cols = [
        "lat-lon",
        "price",
        "currency",
        "price_aprox_local_currency",
        "price_aprox_usd",
        "surface_total_in_m2",
        "surface_covered_in_m2",
        "price_usd_per_m2",
        "price_per_m2",
        "floor",
        "rooms",
        "expenses",
        "properati_url",
    ]
    ​
    keep_cols = ["operation", "property_type", "place_with_parent_names"]
    xxxxxxxxxx
    Next, we'll explore both approaches to subset `mexico_city3`. To retain columns based on `keep_cols`:

    Next, we'll explore both approaches to subset mexico_city3. To retain columns based on keep_cols:

    [ ]:
     
    mexico_city3_subsetted = mexico_city3[keep_cols]
    xxxxxxxxxx
    To drop columns in `drop_cols`:

    To drop columns in drop_cols:

    [ ]:
     
    mexico_city3_subsetted = mexico_city3.drop(columns=drop_cols)
    xxxxxxxxxx
    <font size="+1">Practice</font> 

    Practice

    Create a subset of the DataFrame from mexico-city-real-estate-3.csv which excludes the last two columns.

    [ ]:
     
    ​
    xxxxxxxxxx
    ## Pivot Tables

    Pivot Tables¶

    A pivot table allows us to aggregate and summarize a DataFrame across multiple variables. For example, let's suppose we wanted to calculate the mean of the price column in the mexico_city3 dataset for the different values in the property_type column:

    [ ]:
     
    import numpy as np
    ​
    mexico_city3_pivot = ...
    mexico_city3_pivot
    xxxxxxxxxx
    # Subsetting with Masks

    Subsetting with Masks¶

    Another way to create subsets from a larger dataset is through masking. Masks are ways to filter out the data you're not interested in so that you can focus on the data you are. For example, we might want to look at properties in Colombia that are bigger than 200 square meters. In order to create this subset, we'll need to use a mask.

    First, we'll reset our df1 DataFrame so that we can draw on all the data in its original form. Then we'll create a statement and then assign the result to mask.

    [ ]:
     
    import pandas as pd
    df1 = pd.read_csv("data/colombia-real-estate-1.csv")
    mask = df1["area_m2"] > 200
    mask.head()
    xxxxxxxxxx
    Notice that `mask` is a Series of Boolean values. Where properties are smaller than 200 square meters, our statement evaluates as `False`; where they're bigger than 200, it evaluates to `True`.

    Notice that mask is a Series of Boolean values. Where properties are smaller than 200 square meters, our statement evaluates as False; where they're bigger than 200, it evaluates to True.

    Once we have our mask, we can use it to select all the rows from df1 that evaluate as True.

    [ ]:
     
    df1[mask].head()
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Read colombia-real-estate-2 into a DataFrame named df2, and create a mask to select all the properties that are smaller than 100 square meters.

    [ ]:
     
    ​
    import pandas as pd
    df2 = pd.read_csv("data/colombia-real-estate-2.csv")
    mask = df2["area_m2"]<100
    df2[mask].head()
    xxxxxxxxxx
    We can also create masks with multiple criteria using `&` for "and" and `|` for "or." For example, here's how we would find all properties in Atlántico that are bigger than 400 square meters.

    We can also create masks with multiple criteria using & for "and" and | for "or." For example, here's how we would find all properties in Atlántico that are bigger than 400 square meters.

    [ ]:
     
    mask = (df1["area_m2"] > 400) & (df1["department"] == "Atlántico")
    df1[mask].head()
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Create a mask for df2 to select all the properties in Tolima that are smaller than 150 square meters.

    [ ]:
     
    mask = ...
    df2[mask].head()
    xxxxxxxxxx
    ## Reshape a DataFrame based on column values

    Reshape a DataFrame based on column values¶

    xxxxxxxxxx
    ## What's a pivot table?

    What's a pivot table?¶

    A pivot table allows you to quickly aggregate and summarize a DataFrame using an aggregation function. For example, to build a pivot table that summarizes the mean of the price_cop column for each of the unique categories in the property_type column in df2:

    [ ]:
     
    import numpy as np
    ​
    pivot1 = pd.pivot_table(df2, values="price_cop", index="property_type", aggfunc=np.mean)
    pivot1
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Build a pivot table that summarizes the mean of the price_cop column for each of the unique departments in the department column in df2:

    [ ]:
     
    # REMOVE {
    pivot2 = pd.pivot_table(df2, values="price_cop", index="department", aggfunc=np.mean)
    # REMOVE }
    pivot2
    xxxxxxxxxx
    ## Combine multiple categories in a Series

    Combine multiple categories in a Series¶

    xxxxxxxxxx
    Categorical variables can be collapsed into a fewer number of categories. One approach is to retain the values of the most frequently observed values and collapse all remaining values into a single category. For example, to retain only the values of the top 10 most frequent categories in the `department` column and then collapse the other categories together, use `value_counts` to generate the count of the values:

    Categorical variables can be collapsed into a fewer number of categories. One approach is to retain the values of the most frequently observed values and collapse all remaining values into a single category. For example, to retain only the values of the top 10 most frequent categories in the department column and then collapse the other categories together, use value_counts to generate the count of the values:

    [ ]:
     
    df2["department"].value_counts()
    xxxxxxxxxx
    Next, select just the top 10 using the `head()` method, and select the column names by using the `index` attribute of the series:

    Next, select just the top 10 using the head() method, and select the column names by using the index attribute of the series:

    [ ]:
     
    top_10 = df2["department"].value_counts().head(10).index
    top_10
    xxxxxxxxxx
    Finally, use the apply method and a lambda function to select only the values from the `department` column and collapse the remaining values into the value `Other`:

    Finally, use the apply method and a lambda function to select only the values from the department column and collapse the remaining values into the value Other:

    [ ]:
     
    df2["department"] = df2["department"].apply(lambda x: x if x in top_10 else "Other")
    print(df2["department"].head(20))
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Retain the remaining top 5 most frequent values in the department column and collapse the remaining values into the category Other.

    [ ]:
     
    ​
    xxxxxxxxxx
    # Cross Tabulation

    Cross Tabulation¶

    xxxxxxxxxx
    The pandas `crosstab` function is a useful for working with grouped summary statistics for **categorical** data. It starts by picking two categorical columns, then defines one as the index and the other as the column. If the aggregate function and value column is not defined, `crosstab` will simply calculate the frequency of each combination by default. Let's see the example below from the Colombia real estate dataset.

    The pandas crosstab function is a useful for working with grouped summary statistics for categorical data. It starts by picking two categorical columns, then defines one as the index and the other as the column. If the aggregate function and value column is not defined, crosstab will simply calculate the frequency of each combination by default. Let's see the example below from the Colombia real estate dataset.

    [ ]:
     
    import pandas as pd
    ​
    df = pd.read_csv("data/colombia-real-estate-1.csv")
    df.head()
    xxxxxxxxxx
    The following function calculates the frequency of each combination for two variables, `department` and `property_type`, in the data set.

    The following function calculates the frequency of each combination for two variables, department and property_type, in the data set.

    [ ]:
     
    pd.crosstab(index=df["department"], columns=df["property_type"])
    xxxxxxxxxx
    From the previous example, you can see we have created a DataFrame with the index showing unique observation in variable `department`, while the columns are the unique observation for the variable `property_type`. Each cell shows how many data points there are for each combination of `department` type and `property_type`. For example, there are 8 `apartments` in department `Antioquia`. 

    From the previous example, you can see we have created a DataFrame with the index showing unique observation in variable department, while the columns are the unique observation for the variable property_type. Each cell shows how many data points there are for each combination of department type and property_type. For example, there are 8 apartments in department Antioquia.

    xxxxxxxxxx
    We can further specify a value column and an aggregate function, like in `pivot_table`, to conduct more complicated calculations for the two categorical variables. In the following example, we're looking at the average area size for different property types in the departments in Colombia.

    We can further specify a value column and an aggregate function, like in pivot_table, to conduct more complicated calculations for the two categorical variables. In the following example, we're looking at the average area size for different property types in the departments in Colombia.

    [ ]:
     
    import numpy as np
    ​
    pd.crosstab(
        index=df["department"],
        columns=df["property_type"],
        values=df["area_m2"],
        aggfunc=np.mean,
    ).round(0)
    xxxxxxxxxx
    <font size="+1">Practice</font> 

    Practice

    Create a cross tabulate calculating frequency of combinations from mexico-city-real-estate-3.csv using currency as the index and property_type as the column.

    [ ]:
     
    # Read dataset
    mexico_city3 = ...
    ​
    # Create `crosstab`
    ​
    xxxxxxxxxx
    # Applying Functions to DataFrames and Series

    Applying Functions to DataFrames and Series¶

    xxxxxxxxxx
    `apply` is a useful method for to using one function on all the rows or columns of a DataFrame efficiently. Let's take the following real estate dataset as an example:

    apply is a useful method for to using one function on all the rows or columns of a DataFrame efficiently. Let's take the following real estate dataset as an example:

    [ ]:
     
    # Read data, only use the numerical columns
    df = pd.read_csv("data/colombia-real-estate-2.csv", usecols=["area_m2", "price_cop"])
    df.head()
    xxxxxxxxxx
    By specifying the function inside `apply()`, we can transform the whole DataFrame. For example, I am calculating the square root of each row at each column:

    By specifying the function inside apply(), we can transform the whole DataFrame. For example, I am calculating the square root of each row at each column:

    [ ]:
     
    import numpy as np
    ​
    df.apply(np.sqrt)
    xxxxxxxxxx
    Note you can also specify the `"axis"` inside `apply`. By default, we have `axis=0`, which means we are applying the function to each column. We can also switch to `axis=1` if we want to apply the function to each row. See the following example showing the sum of all rows for each column:

    Note you can also specify the "axis" inside apply. By default, we have axis=0, which means we are applying the function to each column. We can also switch to axis=1 if we want to apply the function to each row. See the following example showing the sum of all rows for each column:

    [ ]:
     
    df.apply(np.sum)
    ​
    xxxxxxxxxx
    The following code will get the sum of all columns for each row:

    The following code will get the sum of all columns for each row:

    [ ]:
     
    df["price_cop"]=df["price_cop"].round(0)
    #df.apply(np.sum, axis=1)
    #df.head()
    df["price_cop"]
    xxxxxxxxxx
    By specifying the column name, we can also apply the function to a specific column or columns. Note that we can also specify index (row names) to only apply functions to specific rows, however, it is not common in practice.

    By specifying the column name, we can also apply the function to a specific column or columns. Note that we can also specify index (row names) to only apply functions to specific rows, however, it is not common in practice.

    [ ]:
     
    df["area_m2"].apply(np.sqrt)
    xxxxxxxxxx
    We can assign the results to a new column:

    We can assign the results to a new column:

    [ ]:
     
    df["area_sqrt"] = df["area_m2"].apply(np.sqrt)
    df
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Create a new column named 'sum_columns', which is the sum of all numerical columns in df:

    [ ]:
     
    df["sum_columns"] = df.apply(np.sum)
    df.head()
    xxxxxxxxxx
    `df.aggregate()`, or `df.agg()`, shares the same concept as `df.apply()` in terms of applying functions to a DataFrame, but `df.aggregate()` can only apply aggregate functions like `sum`, `mean`, `min`, `max`, etc. See the following example for more details:

    df.aggregate(), or df.agg(), shares the same concept as df.apply() in terms of applying functions to a DataFrame, but df.aggregate() can only apply aggregate functions like sum, mean, min, max, etc. See the following example for more details:

    [ ]:
     
    df = pd.read_csv("data/colombia-real-estate-2.csv", usecols=["area_m2", "price_cop"])
    df.head()
    xxxxxxxxxx
    We can check what's the minimum number for each column calling the `min` aggregate function:

    We can check what's the minimum number for each column calling the min aggregate function:

    [ ]:
     
    df.agg("min")
    xxxxxxxxxx
    Like `apply()`, we can also specify the `axis` argument to switch axis:

    Like apply(), we can also specify the axis argument to switch axis:

    [ ]:
     
    df.agg("min", axis=1)
    xxxxxxxxxx
    We can apply aggregate function to a whole DataFrame using `df.agg()`, or specify the column name for a subset of DataFrame:

    We can apply aggregate function to a whole DataFrame using df.agg(), or specify the column name for a subset of DataFrame:

    [ ]:
     
    df["area_m2"].agg("min")
    xxxxxxxxxx
    We can also apply a list of aggregate functions to a DataFrame:

    We can also apply a list of aggregate functions to a DataFrame:

    [ ]:
     
    df.agg(["sum", "max"])
    xxxxxxxxxx
    The syntax above will calculate both `sum` and `max` for each column, and store the result as index. Besides, we can also apply different aggregate functions to different columns. In this case, we need to pass a dictionary specifying key as column names, and value as corresponding aggregate function names:

    The syntax above will calculate both sum and max for each column, and store the result as index. Besides, we can also apply different aggregate functions to different columns. In this case, we need to pass a dictionary specifying key as column names, and value as corresponding aggregate function names:

    [ ]:
     
    df.agg({"area_m2": "sum", "price_cop": "min"})
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Find the minimum for column area_m2 and the maximum for column price_cop using df.agg():

    [ ]:
     
    ​
    xxxxxxxxxx
    # Working with Dates

    Working with Dates¶

    xxxxxxxxxx
    ## Time Stamps

    Time Stamps¶

    xxxxxxxxxx
    Pandas' time series capabilities are built on the `Timestamp` class. For instance, we can transform date strings of different formats into `Timestamp`:

    Pandas' time series capabilities are built on the Timestamp class. For instance, we can transform date strings of different formats into Timestamp:

    [ ]:
     
    print(pd.Timestamp("January 8, 2022"))
    print(pd.Timestamp("01/08/22 20:13"))
    xxxxxxxxxx
    We can also do date math with `Timestamp`s. Note that when we subtract two dates, we get a special object called a `Timedelta`.

    We can also do date math with Timestamps. Note that when we subtract two dates, we get a special object called a Timedelta.

    [ ]:
     
    time_delta = pd.Timestamp("Feb. 11 2016 2:30 am") - pd.Timestamp("2015-08-03 5:14 pm")
    ​
    print("time_delta type:", type(time_delta))
    print(time_delta)
    xxxxxxxxxx
    The pandas `Timestamp` class is also compatible with Python's `datetime` module.

    The pandas Timestamp class is also compatible with Python's datetime module.

    [ ]:
     
    import datetime
    ​
    pd.Timestamp("Feb 11, 2016") - datetime.datetime(2015, 8, 3)
    xxxxxxxxxx
    We can manipulate dates using some function from `offset()`. We can use the `Day()` function to add or decrease days from a `timestamp`. The following function calculates the date that's four days prior to 9 January 2017:

    We can manipulate dates using some function from offset(). We can use the Day() function to add or decrease days from a timestamp. The following function calculates the date that's four days prior to 9 January 2017:

    [ ]:
     
    import pandas as pd
    from pandas.tseries.offsets import BDay, BMonthEnd, Day
    ​
    print(pd.Timestamp("January 9, 2017") - Day(4))
    xxxxxxxxxx
    In some case, you might need to add or subtract only business days. That's when you use `BDay()`:

    In some case, you might need to add or subtract only business days. That's when you use BDay():

    [ ]:
     
    print(pd.Timestamp("January 9, 2017") - BDay(4))
    xxxxxxxxxx
    We can also do an offset at the monthly level. For example, you can use `BMonthEnd(n)` to find the last business day $n$ months later. The following function shows the last business day of January 2017:

    We can also do an offset at the monthly level. For example, you can use BMonthEnd(n) to find the last business day 𝑛n months later. The following function shows the last business day of January 2017:

    [ ]:
     
    print(pd.Timestamp("January 9, 2017") + BMonthEnd(0))
    xxxxxxxxxx
    The following function shows the last business day for May 2017, which is four months after February:

    The following function shows the last business day for May 2017, which is four months after February:

    [ ]:
     
    print(pd.Timestamp("February 9, 2017") + BMonthEnd(4))
    xxxxxxxxxx
    We can also use the `strptime` function inside `datetime` to transform string to date format:

    We can also use the strptime function inside datetime to transform string to date format:

    [ ]:
     
    from datetime import datetime
    ​
    date = datetime.strptime("16 Oct 2022 12:14:05", "%d %b %Y %H:%M:%S")
    date
    xxxxxxxxxx
    We can further transform it into the [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601):

    We can further transform it into the ISO 8601 format:

    [ ]:
     
    date.isoformat()
    xxxxxxxxxx
    ## Date Time Indices

    Date Time Indices¶

    xxxxxxxxxx
    `DatetimeIndex` is a convenient function that transforms date-like data into readable `Datetime` format for a DataFrame index. That way, we can better plot and model time series data. Let's check an example. Here we have Apple's stock prices from 1999 10 2022. 

    DatetimeIndex is a convenient function that transforms date-like data into readable Datetime format for a DataFrame index. That way, we can better plot and model time series data. Let's check an example. Here we have Apple's stock prices from 1999 10 2022.

    [ ]:
     
    data = pd.read_csv("data/AAPL.csv")
    data.set_index("date", inplace=True)
    ​
    print(data.info())
    data.head()
    xxxxxxxxxx
    Even though the index looks like *dates*, it is not in *date format*. So when we plot the data, the index doesn't follow the sequence of years, and the x-axis ticks are hard to read.

    Even though the index looks like dates, it is not in date format. So when we plot the data, the index doesn't follow the sequence of years, and the x-axis ticks are hard to read.

    [ ]:
     
    data["open"].plot()
    xxxxxxxxxx
    You can see the index is not in date format.

    You can see the index is not in date format.

    [ ]:
     
    data.index[:5]
    xxxxxxxxxx
    We can use the `DatetimeIndex` function to transform it into date:

    We can use the DatetimeIndex function to transform it into date:

    [ ]:
     
    pd.DatetimeIndex(data.index)
    xxxxxxxxxx
    And we can set it as the index:

    And we can set it as the index:

    [ ]:
     
    data.index = pd.DatetimeIndex(data.index)
    data
    xxxxxxxxxx
    Now we can see the benefit from plotting:

    Now we can see the benefit from plotting:

    [ ]:
     
    data["open"].plot()
    xxxxxxxxxx
    ## Date Ranges

    Date Ranges¶

    xxxxxxxxxx
    If we're entering time series data into a DataFrame, it will often be useful to create a range of dates using `date_range`. We can create it with different frequencies by specifying `freq`. Here are the days in a specific range:

    If we're entering time series data into a DataFrame, it will often be useful to create a range of dates using date_range. We can create it with different frequencies by specifying freq. Here are the days in a specific range:

    [ ]:
     
    pd.date_range(start="1/8/2022", end="3/2/2022", freq="D")
    xxxxxxxxxx
    Here are the business dates for a specific range:

    Here are the business dates for a specific range:

    [ ]:
     
    pd.date_range(start="1/8/2022", end="3/2/2022", freq="B")
    xxxxxxxxxx
    # References & Further Reading

    References & Further Reading¶

    • Pandas Documentation on Dropping a Column in a DataFrame
    • Pandas Documentation on Printing out the First Few Rows of a DataFrame
    • Pandas Documentation on Reading in a CSV File Into a DataFrame
    • Getting Started with Pandas Documentation
    • Pandas Documentation on Extracting a Column to a Series
    • Pandas Official Indexing Guide
    • Series in pandas
    • Tutorial for value_counts
    • Tutorial for groupby
    • pandas Documentation for groupby
    • Pandas Official Indexing Guide
    • Online Examples of Selecting Numeric Columns of a DataFrame
    • Official Pandas Documentation on Data Types in a DataFrame
    • Pandas documentation for Boolean indexing
    • More information on creating various kinds of subsets
    • More on boolean indexing
    • A tutorial on using various criteria to create subsets
    • Pandas.DataFrame.apply
    • Pandas.DataFrame.aggregate
    xxxxxxxxxx
    ---

    Copyright © 2022 WorldQuant University. This content is licensed solely for personal use. Redistribution or publication of this material is strictly prohibited.

    xxxxxxxxxx
    <font size="+3"><strong>Pandas: Descriptive Statistics</strong></font>

    Pandas: Descriptive Statistics

    xxxxxxxxxx
    # Descriptive Statistics 

    Descriptive Statistics¶

    xxxxxxxxxx
    *Descriptive statistics* are used to describe the basic features of a dataset.

    Descriptive statistics are used to describe the basic features of a dataset.

    xxxxxxxxxx
    ## Quartiles

    Quartiles¶

    Quartiles divide a sequence of numbers into four equal parts. Grouping a dataset into quartiles helps us find outliers and provides the basis for the data in a boxplot.

    xxxxxxxxxx
    ## Series

    Series¶

    A series is a one-dimensional array that can hold any type of data. We'll generally use the term to refer to a column in a dataset that's arranged in a table. In fact, this is the reason why, in the library pandas, DataFrame columns are called Series. In a pandas Series, all items in the array must be the same data type.

    xxxxxxxxxx
    ## The Mean

    The Mean¶

    All the data points in a dataset can be added together and then divided by the total number of data points to find the mean. You might be used to calling this number an average, but the two ideas are the same. Means help us understand the central tendency of a dataset.

    xxxxxxxxxx
    ## Skewed Distributions

    Skewed Distributions¶

    For any given activity, there is a range of probable outcomes. All other things being equal, we would expect most of the outcomes to fall in the middle of the possible range, with the number of outcomes diminishing on either side of the peak. In statistics, this is known as a normal distribution, but you may have heard it called a bell curve, because it looks like a bell. Here's an example:

    xxxxxxxxxx
    ![download.png](attachment:d5849b41-6221-4f3f-9d74-1066ebdf1a0d.png)![image.png](data:image/png;base64,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)

    download.pngimage.png

    xxxxxxxxxx
    A **skewed distribution** is a type of distribution where the peak of the curve is shifted, or *skewed*, either to the right or the left of the distribution. Here's an example:

    A skewed distribution is a type of distribution where the peak of the curve is shifted, or skewed, either to the right or the left of the distribution. Here's an example:

    xxxxxxxxxx
    ![image.png](data:image/png;base64,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)

    image.png

    xxxxxxxxxx
    ## Variance

    Variance¶

    xxxxxxxxxx
    **Variance** (which is sometimes called **volatility** in finance), is a measurement of how spread out the points in a dataset are around the mean. It's calculated by first summing up the squared different between each data point and mean, then dividing by the number of data points minus 1.

    Variance (which is sometimes called volatility in finance), is a measurement of how spread out the points in a dataset are around the mean. It's calculated by first summing up the squared different between each data point and mean, then dividing by the number of data points minus 1.

    Var(𝑋)=1𝑛−1∑𝑘=1𝑛(𝑥𝑘−𝑋⎯⎯⎯⎯⎯)2Var(X)=1n−1∑k=1n(xk−X¯)2

    xxxxxxxxxx
    Here's a great video that shows how to calculate variance:

    Here's a great video that shows how to calculate variance:

    [1]:
     
    from IPython.display import YouTubeVideo
    ​
    YouTubeVideo("deIQeQzPK08", width=600)
    [1]:
    xxxxxxxxxx
    # We can get the variance for any numerical column in a DataFrame by using the `.var()` function:

    We can get the variance for any numerical column in a DataFrame by using the .var() function:¶

    [2]:
     
    import pandas as pd
    ​
    df = pd.read_csv("data/colombia-real-estate-1.csv")
    df.head()
    [2]:
    property_type department lat lon area_m2 price_usd
    0 house Bogotá D.C 4.690 -74.048 187.0 $330,899.98
    1 house Bogotá D.C 4.695 -74.082 82.0 $121,555.09
    2 house Quindío 4.535 -75.676 235.0 $219,474.47
    3 house Bogotá D.C 4.620 -74.129 195.0 $97,919.38
    4 house Atlántico 11.012 -74.834 112.0 $115,477.34
    [3]:
     
    df["area_m2"].var()
    [3]:
    8057.128914931936
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Get the variance of the lat and lon column.

    [ ]:
     
    ​
    ​
    xxxxxxxxxx
    Because variance is the squared deviation from the mean, it's heavily influenced by outliers. When the difference between the outliers and the mean are too far away from each other, the variance might not reveal the true information of how data points are distributed. In this case, we can calculate **trimmed variance** instead. Trimmed variance is the variance calculated excluding the largest and the smallest data points. Using `trimmed_var` function in the SciPy library, we can calculate the trimmed variance for a column.

    Because variance is the squared deviation from the mean, it's heavily influenced by outliers. When the difference between the outliers and the mean are too far away from each other, the variance might not reveal the true information of how data points are distributed. In this case, we can calculate trimmed variance instead. Trimmed variance is the variance calculated excluding the largest and the smallest data points. Using trimmed_var function in the SciPy library, we can calculate the trimmed variance for a column.

    [4]:
     
    from scipy import stats
    ​
    stats.mstats.trimmed_var(df["area_m2"])
    [4]:
    3822.0997717413616
    xxxxxxxxxx
    Note the trimmed variance is much smaller than the variance.

    Note the trimmed variance is much smaller than the variance.

    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Get the trimmed variance for lat.

    [ ]:
     
    ​
    xxxxxxxxxx
    ## Standard Deviation

    Standard Deviation¶

    Standard deviation describes the proportion of records above or below the mean of a given distribution. In a normal distribution, 68% of the values fall within one standard deviation of the mean, 95% of the values fall within two standard deviations from the mean, and 99.7% of the values fall within three standard deviations from the mean.

    xxxxxxxxxx
    Mention that, in finance, standard deviation can be called **volatility**.

    Mention that, in finance, standard deviation can be called volatility.

    xxxxxxxxxx
    ## Outliers

    Outliers¶

    An outlier is a value in a dataset that falls well beyond the dataset mean — more than three standard deviations. Depending on the analytical strategy, it might be useful to drop outliers from a dataset, because their extreme deviation from the mean can result in misleading conclusions.

    xxxxxxxxxx
     ## Categorical Data

    Categorical Data¶

    Categorical data is any type of data that can only be represented by distinct values. Eye color, handedness, and academic attainment are all categorical variables. The other kind of variable is called a continuous variable. Continuous variables can have an infinite number of values, whereas categorical variables have concrete values. For this reason, categorical values require special attention in statistical analysis.

    xxxxxxxxxx
    ## Location Data

    Location Data¶

    Location data is information about a datapoint’s location in space, and can be expressed in latitude/longitude pairs, street address, altitude, or any other place-specific identifiers.

    xxxxxxxxxx
    ## Numerical Data

    Numerical Data¶

    Numerical data is any information that can be represented by numbers.

    xxxxxxxxxx
    # Summary Statistics

    Summary Statistics¶

    xxxxxxxxxx
    ## Summary Statistics

    Summary Statistics¶

    Summary statistics are a set of simple calculations that help data scientists understand the broad strokes of their datasets.

    xxxxxxxxxx
    ## Working with Summary Statistics

    Working with Summary Statistics¶

    To calculate summary statistics in pandas, use the describe method. We can generate summary statistics for the colombia-real-estate-1 dataset with code that looks like this:

    [5]:
     
    import pandas as pd
    ​
    df1 = pd.read_csv("data/colombia-real-estate-1.csv")
    df1.describe()
    [5]:
    lat lon area_m2
    count 2967.000000 2967.000000 3066.000000
    mean 5.938198 -74.512216 171.249511
    std 2.638133 2.510621 89.761511
    min -0.001299 -81.385000 64.000000
    25% 4.671000 -74.823000 100.000000
    50% 4.717000 -74.078000 145.000000
    75% 5.070000 -74.049000 222.000000
    max 13.333000 0.001431 449.000000
    xxxxxxxxxx
    By default, the `describe`method will return `count`, `mean`, `standard deviations`, `minimum values`and `maximum values`. Also by default, the ignores ignores non-numerical columns.

    By default, the describemethod will return count, mean, standard deviations, minimum valuesand maximum values. Also by default, the ignores ignores non-numerical columns.

    Practice

    Try it yourself! Using the colombia-real-estate-2 dataset, create a DataFrame called df2, and print the resulting summary statistics.

    [6]:
     
    df2 = pd.read_csv("data/colombia-real-estate-2.csv")
    ​
    xxxxxxxxxx
    # Calculate the Quantiles for a Series

    Calculate the Quantiles for a Series¶

    xxxxxxxxxx
    Quantiles allow you to summarize the distribution of numerical values in a series. The `n'th` quantile divides an ordered series into `n` portions, each with the same number of entries. The boundaries between these portions are known as quantiles. Let's load a dataset to see how this works in practice:

    Quantiles allow you to summarize the distribution of numerical values in a series. The n'th quantile divides an ordered series into n portions, each with the same number of entries. The boundaries between these portions are known as quantiles. Let's load a dataset to see how this works in practice:

    [7]:
     
    mexico_city2 = pd.read_csv("./data/mexico-city-real-estate-2.csv")
    mexico_city2.head
    [7]:
    <bound method NDFrame.head of      operation property_type  \
    0         sell     apartment   
    1         sell     apartment   
    2         sell     apartment   
    3         sell     apartment   
    4         sell     apartment   
    ...        ...           ...   
    3786      sell     apartment   
    3787      sell     apartment   
    3788      sell     apartment   
    3789      sell     apartment   
    3790      sell     apartment   
    
                                    place_with_parent_names  \
    0               |México|Distrito Federal|Benito Juárez|   
    1                     |México|Distrito Federal|Tlalpan|   
    2     |México|Distrito Federal|Álvaro Obregón|Tetelpan|   
    3               |México|Distrito Federal|Benito Juárez|   
    4               |México|Distrito Federal|Benito Juárez|   
    ...                                                 ...   
    3786               |México|Distrito Federal|Cuauhtémoc|   
    3787            |México|Distrito Federal|Benito Juárez|   
    3788            |México|Distrito Federal|Benito Juárez|   
    3789               |México|Distrito Federal|Iztapalapa|   
    3790            |México|Distrito Federal|Benito Juárez|   
    
                               lat-lon      price currency  \
    0            19.375445,-99.1543144  4300500.0      MXN   
    1     19.2742408542,-99.1496908665  2788000.0      MXN   
    2     19.3427189674,-99.2225289345  3351600.0      MXN   
    3           19.3596034,-99.1514055  2862800.0      MXN   
    4           19.3953378,-99.1560855  3204800.0      MXN   
    ...                            ...        ...      ...   
    3786           19.418578,-99.17134  1893000.0      MXN   
    3787          19.377383,-99.144978  1425559.0      MXN   
    3788             19.40012,-99.1532  1774000.0      MXN   
    3789           19.353937,-99.06196   841000.0      MXN   
    3790           19.35782,-99.149406  1295000.0      MXN   
    
          price_aprox_local_currency  price_aprox_usd  surface_total_in_m2  \
    0                     4261596.45        226578.22                  0.0   
    1                     2762778.87        146889.91                  0.0   
    2                     3321280.35        176584.01                  0.0   
    3                     2836902.23        150830.86                 73.0   
    4                     3175808.45        168849.64                  0.0   
    ...                          ...              ...                  ...   
    3786                  1875875.33         99735.51                  NaN   
    3787                  1412662.98         75107.69                  NaN   
    3788                  1757951.87         93465.82                  NaN   
    3789                   833392.03         44309.33                  NaN   
    3790                  1283284.95         68228.99                  NaN   
    
          surface_covered_in_m2  price_usd_per_m2  price_per_m2  floor  rooms  \
    0                      74.0               NaN  58114.864865    NaN    NaN   
    1                     111.0               NaN  25117.117117    NaN    NaN   
    2                      82.0               NaN  40873.170732    NaN    NaN   
    3                      73.0       2066.176164  39216.438356    NaN    NaN   
    4                      63.0               NaN  50869.841270    NaN    NaN   
    ...                     ...               ...           ...    ...    ...   
    3786                   98.0               NaN  19316.326531    NaN    NaN   
    3787                   60.0               NaN  23759.316667    NaN    NaN   
    3788                   84.0               NaN  21119.047619    NaN    NaN   
    3789                   66.0               NaN  12742.424242    NaN    NaN   
    3790                   80.0               NaN  16187.500000    NaN    NaN   
    
          expenses                                      properati_url  
    0          NaN  http://benito-juarez.properati.com.mx/l1yl_ven...  
    1          NaN  http://tlalpan.properati.com.mx/l1ym_venta_dep...  
    2          NaN  http://tetelpan.properati.com.mx/l1yn_venta_de...  
    3          NaN  http://benito-juarez.properati.com.mx/l1yo_ven...  
    4          NaN  http://benito-juarez.properati.com.mx/l1yp_ven...  
    ...        ...                                                ...  
    3786       NaN  http://cuauhtemoc.properati.com.mx/o45k_venta_...  
    3787       NaN  http://benito-juarez.properati.com.mx/o45l_ven...  
    3788       NaN  http://benito-juarez.properati.com.mx/o45m_ven...  
    3789       NaN  http://iztapalapa.properati.com.mx/o45n_venta_...  
    3790       NaN  http://benito-juarez.properati.com.mx/o45t_ven...  
    
    [3791 rows x 16 columns]>
    xxxxxxxxxx
    To examine quantiles, let's pick the price column

    To examine quantiles, let's pick the price column

    [8]:
     
    price = mexico_city2["price"]
    price
    [8]:
    0       4300500.0
    1       2788000.0
    2       3351600.0
    3       2862800.0
    4       3204800.0
              ...    
    3786    1893000.0
    3787    1425559.0
    3788    1774000.0
    3789     841000.0
    3790    1295000.0
    Name: price, Length: 3791, dtype: float64
    xxxxxxxxxx
    The median is the middle entry in the ordered list of prices:

    The median is the middle entry in the ordered list of prices:

    [9]:
     
    price.quantile(0.5)
    [9]:
    2273333.0
    xxxxxxxxxx
    ## Quartiles

    Quartiles¶

    A commonly used set of quantiles are the fourth quantiles known as quartiles. You can also find the minimum, first quartile, median, third quartile and maximum values in a series (which are typically the values used to create a boxplot):

    [10]:
     
    price.quantile([0, 0.25, 0.5, 0.75, 1])
    [10]:
    0.00      200000.0
    0.25     1180000.0
    0.50     2273333.0
    0.75     4500000.0
    1.00    78000000.0
    Name: price, dtype: float64
    xxxxxxxxxx
    *Practice* What's the 0.7 quantile in the price column of `mexico-city-real-estate-3.csv`?

    Practice What's the 0.7 quantile in the price column of mexico-city-real-estate-3.csv?

    [11]:
     
    mexico_city3 = ...
    price = ...
    print(price.quantile(...))
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    Cell In [11], line 3
          1 mexico_city3 = ...
          2 price = ...
    ----> 3 print(price.quantile(...))
    
    AttributeError: 'ellipsis' object has no attribute 'quantile'
    xxxxxxxxxx
    # Correlations

    Correlations¶

    xxxxxxxxxx
    **Correlations** tell us about the relationship between two sets of data. When we calculate this relationship, the result is a **correlation coefficient**. Correlation coefficients can have any value between -1 and 1. Values above 0 indicate a positive relationship (as one variable goes up, the other does too), and values below 0 indicate a negative relationship (as one variable goes up, the other goes down). The closer the coefficient's value is to either 1 or -1, the stronger the relationship is; the closer the coefficient's value is to 0, the weaker the relationship is. Coefficients equal to 0 indicate that there is no relationship between the two values, and are accordingly quite rare.

    Correlations tell us about the relationship between two sets of data. When we calculate this relationship, the result is a correlation coefficient. Correlation coefficients can have any value between -1 and 1. Values above 0 indicate a positive relationship (as one variable goes up, the other does too), and values below 0 indicate a negative relationship (as one variable goes up, the other goes down). The closer the coefficient's value is to either 1 or -1, the stronger the relationship is; the closer the coefficient's value is to 0, the weaker the relationship is. Coefficients equal to 0 indicate that there is no relationship between the two values, and are accordingly quite rare.

    Let's run a correlation on some of the data from the colombia-real-estate-2 dataset. We might suspect that there is some kind of relationship between the price of a property and the area it occupies, so we'll use the Series.corr method to figure it out. The code looks like this:

    [ ]:
     
    area_m2 = df2["area_m2"]
    price_cop = df2["price_cop"]
    correlation = area_m2.corr(price_cop)
    print(correlation)
    xxxxxxxxxx
    The correlation coefficient here is about 0.519, which is a moderate, positive correlation. That is, as the area of a property goes up, so does the price. If the result had been a negative number, we would be able to say that as the area goes up, the price goes down.

    The correlation coefficient here is about 0.519, which is a moderate, positive correlation. That is, as the area of a property goes up, so does the price. If the result had been a negative number, we would be able to say that as the area goes up, the price goes down.

    Practice Try it yourself! Find the relationship between "area_m2" and "price_usd" in the colombia-real-estate-3 dataset, and interpret the resulting coefficient.

    [ ]:
     
    df3 = ...
    print(correlation)
    xxxxxxxxxx
    # References & Further Reading

    References & Further Reading¶

    • Brief Descriptions of Central Tendency
    • Pandas Documentation on Summary Statistics
    • Pandas Documentation on Quantiles
    • Background on Correlations
    xxxxxxxxxx
    ---

    Copyright © 2022 WorldQuant University. This content is licensed solely for personal use. Redistribution or publication of this material is strictly prohibited.

    xxxxxxxxxx
    <font size="+3"><strong>Visualizing Data: Matplotlib</strong></font>

    Visualizing Data: Matplotlib

    xxxxxxxxxx
    There are many ways to interact with data, and one of the most powerful modes of interaction is through **visualizations**. Visualizations show data graphically, and are useful for exploring, analyzing, and presenting datasets. We use four libraries for making visualizations: [pandas](../%40textbook/07-visualization-pandas.ipynb), Matplotlib, [plotly express](../%40textbook/08-visualization-plotly.ipynb), and [seaborn](../%40textbook/09-visualization-seaborn.ipynb). In this section, we'll focus on using Matplotlib.

    There are many ways to interact with data, and one of the most powerful modes of interaction is through visualizations. Visualizations show data graphically, and are useful for exploring, analyzing, and presenting datasets. We use four libraries for making visualizations: pandas, Matplotlib, plotly express, and seaborn. In this section, we'll focus on using Matplotlib.

    Boxplots¶

    A boxplot is a graph that shows the minimum, first quartile, median, third quartile, and the maximum values in a dataset. Boxplots are useful, as they provide a visual summary of the data enabling researchers to quickly identify mean values, the dispersion of the data set, and signs of skewness.

    Let's create a boxplot using the "area_m2" data from colombia-real-estate-1. Note that the usecols argument allow us to only read in the columns we want, in this case "area_m2".

    [1]:
     
    import matplotlib.pyplot as plt
    import pandas as pd
    ​
    df = pd.read_csv("data/colombia-real-estate-1.csv", usecols=["area_m2"])
    plt.boxplot(df["area_m2"])
    plt.ylabel("Area [sq. meters]")
    plt.title("Area in Square Meters");
    xxxxxxxxxx
    Here's how to interpret a boxplot. A boxplot always contains a box with two lines above and below it. Those lines are called **whiskers**, and the ends of those lines represent the upper and lower bounds of the dataset. There are also some values above the top whisker, but we'll come back to them a little later. In the meantime, notice that the plot divides the data into four sections: 

    Here's how to interpret a boxplot. A boxplot always contains a box with two lines above and below it. Those lines are called whiskers, and the ends of those lines represent the upper and lower bounds of the dataset. There are also some values above the top whisker, but we'll come back to them a little later. In the meantime, notice that the plot divides the data into four sections:

    • the bottom of the lower whisker to the bottom of the box,
    • the bottom of the box to the line in the middle,
    • the middle line to the top line of the box, and
    • the top of the box to the end of the upper whisker.

    These sections are called intervals, and the three lines that divide them are called quartiles. Each interval contains 25% of the observations in the dataset, which means that the box created by the first and third quartiles represents the middle 50% of observations. The second quartile — the orange line on our graph — represents the dataset's median value.

    Keeping all that in mind, the boxplot here shows that while there are some very large properties in our Colombia dataset, half the properties tend to be much smaller. Remember those data points above the top whisker? Those are called outliers, and they represent values so extreme that they fall outside the meaningful spread of observations in the dataset. Since half of our properties are small — one-bedroom apartments, say — we don't really need to pay attention to eighty-room mansions nestled in the middle of thousand-acre estates. Not for this project anyway. Part of the purpose of making a boxplot is to find those outliers and discard them from future analyses.

    Practice

    Try it yourself! Create a boxplot using the price_cop data from colombia-real-estate-2.

    [2]:
     
    df2 = ...
    ​
    ​
    xxxxxxxxxx
    # Histograms

    Histograms¶

    A histogram is a graph that shows the frequency distribution of numerical data. In addition to helping us understand frequency, histograms are also useful for detecting outliers.

    Let's create a histogram using the "area_m2" data from colombia-real-estate-1.

    [3]:
     
    df = pd.read_csv("data/colombia-real-estate-1.csv", usecols=["area_m2"])
    plt.hist(df, bins=10, rwidth=0.9, color="b")
    plt.title("The Area of Real Estate in Colombia")
    plt.xlabel("Property Area")
    plt.ylabel("Number of Properties")
    plt.grid(axis="y", alpha=0.75);
    xxxxxxxxxx
    There are two things to consider in this histogram. First and foremost is its interpretation: the distribution is skewed significantly to the left. In this case, the data suggest that the majority of properties in Colombia are smaller than 150 square meters.

    There are two things to consider in this histogram. First and foremost is its interpretation: the distribution is skewed significantly to the left. In this case, the data suggest that the majority of properties in Colombia are smaller than 150 square meters.

    You might also have noticed that there are ten bars. In a histogram, we call these bars bins. A bin is simply a way to group data to make it easier to see trends. You can use as many or as few as you like; just recognize that the fewer bins you use, the less detailed the output will become.

    Let's take a look at what the same data looks like when it's in 20 bins instead of 10.

    [4]:
     
    df = pd.read_csv("data/colombia-real-estate-1.csv", usecols=["area_m2"])
    # Notice that we changed `bins` from 10 to 20
    plt.hist(df, bins=20, rwidth=0.9, color="b")
    plt.title("The Area of Real Estate in Colombia")
    plt.xlabel("Property Area")
    plt.ylabel("Number of Properties")
    plt.grid(axis="y", alpha=0.75);
    xxxxxxxxxx
    The line isn't quite as smooth as it was before. Depending on the story you want your histogram to tell, it might be more important for a trend to be smooth, or it might be more important to show smaller levels of variation. Here, we can still see that the properties in Colombia tend to be small, but the size of the properties are not evenly distributed. This histogram doesn't tell us anything about *why* this might be true, but it does suggest that there might be something more interesting going on in the background.

    The line isn't quite as smooth as it was before. Depending on the story you want your histogram to tell, it might be more important for a trend to be smooth, or it might be more important to show smaller levels of variation. Here, we can still see that the properties in Colombia tend to be small, but the size of the properties are not evenly distributed. This histogram doesn't tell us anything about why this might be true, but it does suggest that there might be something more interesting going on in the background.

    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Create two histograms using the price_cop data from Colombia Real Estate 2: one with five bins, and the other with 15 bins.

    [5]:
     
    df2 = ...
    ​
    plt.title("The Area of Real Estate in Colombia")
    plt.xlabel("Property Area")
    plt.ylabel("Number of Properties")
    plt.grid(axis="y", alpha=0.75)
    [6]:
     
    ​
    plt.title("The Area of Real Estate in Colombia")
    plt.xlabel("Propert Area")
    plt.ylabel("Number of Properties")
    plt.grid(axis="y", alpha=0.75)
    xxxxxxxxxx
    # Bar Charts

    Bar Charts¶

    A bar chart is a graph that shows all the values of a categorical variable in a dataset. They consist of an axis and a series of labeled horizontal or vertical bars. The bars depict frequencies of different values of a variable or simply the different values themselves. The numbers on the y-axis of a vertical bar chart or the x-axis of a horizontal bar chart are called the scale.

    Let's make a bar chart showing the number of properties in each borough, using the mexico-city-real-estate-1 dataset. First, let's pull out the values in the borough variable.

    [7]:
     
    mexico_city1 = pd.read_csv("./data/mexico-city-real-estate-1.csv")
    ​
    new_columns = ["empty1", "country", "city", "borough", "empty2"]
    mexico_city1[new_columns] = mexico_city1["place_with_parent_names"].str.split(
        "|", 4, expand=True
    )
    mexico_city1 = mexico_city1.drop(["empty1", "empty2"], axis=1)
    ​
    by_borough = mexico_city1["borough"].value_counts()
    by_borough
    /tmp/ipykernel_510/3713566005.py:4: FutureWarning: In a future version of pandas all arguments of StringMethods.split except for the argument 'pat' will be keyword-only.
      mexico_city1[new_columns] = mexico_city1["place_with_parent_names"].str.split(
    
    [7]:
    Miguel Hidalgo            757
    Benito Juárez             444
    Álvaro Obregón            420
    Cuajimalpa de Morelos     417
    Iztapalapa                299
    Tlalpan                   295
    Cuauhtémoc                256
    Gustavo A. Madero         197
    Coyoacán                  159
    Tláhuac                   152
    Venustiano Carranza       123
    La Magdalena Contreras     73
    Xochimilco                 70
    Azcapotzalco               62
    Iztacalco                  61
                                5
    Milpa Alta                  1
    Name: borough, dtype: int64
    xxxxxxxxxx
    Now, let's make a bar chart of the Series.

    Now, let's make a bar chart of the Series.

    [8]:
     
    plt.bar(x=by_borough.index, height=by_borough.values)
    plt.ylabel("Number of Properties")
    plt.xticks(rotation=90);
    xxxxxxxxxx
    <font size="+1">Practice</font> 

    Practice

    Try it yourself! Make a bar chart by property types in the mexico-city-real-estate-1.csv dataset.

    [9]:
     
    by_property_type = ...
    ​
    plt.ylabel("Number of Properties")
    plt.xticks(rotation=90);
    xxxxxxxxxx
    # Scatter Plots

    Scatter Plots¶

    A scatter plot is a graph that uses dots to represent values for two different numeric variables. The position of each dot on the horizontal and vertical axis indicates values for an individual data point. Scatter plots are used to observe relationships between variables, and are especially useful if you're looking for correlations.

    Let's create a simple scatter plot using the "area_m2" data from colombia-real-estate-3. Note that we use the usecols argument below to import only two columns from the CSV file.

    [10]:
    xxxxxxxxxx
     
    df1 = pd.read_csv("data/colombia-real-estate-3.csv", usecols=["area_m2", "price_usd"])
    plt.scatter(df1["area_m2"], df1["price_usd"], color="r")
    plt.xlabel("Property Area")
    plt.ylabel("Price in US Dollars")
    plt.title("Property Area vs Price in US Dollars");
    xxxxxxxxxx
    This scatter plot is showing us what we might already have suspected: there are lots of cheap, small properties, and as the size of the property goes up, so does the price. 

    This scatter plot is showing us what we might already have suspected: there are lots of cheap, small properties, and as the size of the property goes up, so does the price.

    Practice

    Try it yourself! Create a scatter plot using the area_m2 and price_cop columns in the colombia-real-estate-2 dataset. Try changing the color argument to "b", "g", or "y", and see what happens!

    [11]:
    xxxxxxxxxx
     
    df2 = pd.read_csv(
        "data/colombia-real-estate-2.csv", usecols=["area_m2", "price_cop"]
    )  # REMOVERHS
    ​
    plt.xlabel("Property Area")
    plt.ylabel("Price in Colombian Pesos")
    plt.title("Property Area vs Price in Colombian Pesos");
    xxxxxxxxxx
    You may have noticed that there are lots of data points here, which makes it difficult to see if there's really a trend going on. This issue is called **over-plotting**, and it's very common in large datasets.

    You may have noticed that there are lots of data points here, which makes it difficult to see if there's really a trend going on. This issue is called over-plotting, and it's very common in large datasets.

    One solution to over-plotting is to use the df.sample method to select a random sample from the dataset. In the example below, note that the frac=0.50 indicates that the random sample will consist of 50% of the data points in the set.

    [12]:
    xxxxxxxxxx
     
    df3 = pd.read_csv("data/colombia-real-estate-3.csv", usecols=["area_m2", "price_usd"])
    df4 = df3.sample(frac=0.50, replace=True, random_state=1)
    plt.scatter(df4["area_m2"], df4["price_usd"], color="r")
    plt.xlabel("Property Area")
    plt.ylabel("Price in US Dollars")
    plt.title("Property Area vs Price in US Dollars");
    xxxxxxxxxx
    That looks better, but things are still a little muddled. Let's try for a random sample that only looks at 25% of the data.

    That looks better, but things are still a little muddled. Let's try for a random sample that only looks at 25% of the data.

    [13]:
    xxxxxxxxxx
     
    df4 = df3.sample(frac=0.25, replace=True, random_state=1)
    plt.scatter(df4["area_m2"], df4["price_usd"], color="r")
    plt.xlabel("Property Area")
    plt.ylabel("Price in US Dollars")
    plt.title("Property Area vs Price in US Dollars");
    xxxxxxxxxx
    Perfect! With a smaller sample, it's much easier to see what's going on. 

    Perfect! With a smaller sample, it's much easier to see what's going on.

    Practice

    Try it yourself! Using a random sample of 25% of the area_m2 and price_cop data from the colombia-rea-estate-3 data, create a green scatter plot.

    [14]:
    xxxxxxxxxx
     
    df3 = pd.read_csv("data/colombia-real-estate-3.csv", usecols=["area_m2", "price_usd"])
    df4 = ...
    ​
    xxxxxxxxxx
    With the data as broadly scattered as this, it's unlikely that the two variables share a strong positive correlation, but let's calculate the **correlation coefficient,** just to be sure. 

    With the data as broadly scattered as this, it's unlikely that the two variables share a strong positive correlation, but let's calculate the correlation coefficient, just to be sure.

    To do that, we'll go back to the full dataset, and use the corr method. The code looks like this:

    [15]:
    xxxxxxxxxx
     
    df = pd.read_csv("data/colombia-real-estate-3.csv", usecols=["area_m2", "price_usd"])
    area_m2 = df["area_m2"]
    price_usd = df["price_usd"]
    correlation = area_m2.corr(price_usd)
    print(correlation)
    0.5250881127255975
    
    xxxxxxxxxx
    This indicates a moderate positive correlation between `"area_m2"` and `"price_usd"`, which is consistent with the scatter plot we made.

    This indicates a moderate positive correlation between "area_m2" and "price_usd", which is consistent with the scatter plot we made.

    Practice

    Try it yourself! Use the corr method to find the correlation coefficient of "area_m2" and "price_cop" in the colombia-real-estate-3 dataset.

    [16]:
    xxxxxxxxxx
     
    import pandas as pd
    ​
    df = pd.read_csv("data/colombia-real-estate-3.csv", usecols=["area_m2", "price_usd"])
    area_m2 = df["area_m2"]
    price_usd = df["price_usd"]
    correlation = ...
    print(correlation)
    Ellipsis
    
    xxxxxxxxxx
    # Add a vertical or horizontal line across a plot

    Add a vertical or horizontal line across a plot¶

    xxxxxxxxxx
    Vertical lines can be added to an existing plot in Matplotlib using `plt.axvline`. For this example, we'll add a vertical line using the scatter plot created in the previous step:

    Vertical lines can be added to an existing plot in Matplotlib using plt.axvline. For this example, we'll add a vertical line using the scatter plot created in the previous step:

    [17]:
    xxxxxxxxxx
     
    plt.scatter(df4["area_m2"], df4["price_usd"], color="r")
    plt.axvline(250, linestyle="--", color="blue", label="Line Title")
    plt.xlabel("Property Area")
    plt.ylabel("Price in US Dollars")
    plt.title("Property Area vs Price in US Dollars");
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    Cell In [17], line 1
    ----> 1 plt.scatter(df4["area_m2"], df4["price_usd"], color="r")
          2 plt.axvline(250, linestyle="--", color="blue", label="Line Title")
          3 plt.xlabel("Property Area")
    
    TypeError: 'ellipsis' object is not subscriptable
    xxxxxxxxxx
    <font size="+1">Practice</font>

    Practice

    Try it yourself! Use the axvline function to plot a vertical line at 350 and at 150 on the x-axis using the same scatter plot from the previous example.

    [ ]:
    xxxxxxxxxx
     
    # Remove {
    plt.scatter(df4["area_m2"], df4["price_usd"], color="r")
    plt.axvline(150, linestyle="--", color="blue", label="Line Title")
    plt.axvline(350, linestyle="--", color="blue", label="Line Title")
    plt.xlabel("Property Area")
    plt.ylabel("Price in US Dollars")
    plt.title("Property Area vs Price in US Dollars");
    # Remove }
    xxxxxxxxxx
    # Sources & Further Reading

    Sources & Further Reading¶

    • Example Boxplot from the Matplotlib Documentation
    • Discussion of How to Interpret a Histogram
    • Python Documentation on Histograms
    • Pandas Official DataFrame Bar Plot Documentation
    • Online Tutorial on Using Groupby and Count in Pandas
    • Pandas Official Documentation on Splitting a String Entry in a Column
    • Wikipedia Entry on Boroughs of Mexico City
    • stackoverflow Entry on Adding Labels and Titles to Pandas Plots
    • Further Information on Scatter Plots
    • Scatter Plot Tutorial
    • Subsetting Random Samples in a DataFrame
    • Pandas corr Documentation
    xxxxxxxxxx
    ---

    Copyright © 2022 WorldQuant University. This content is licensed solely for personal use. Redistribution or publication of this material is strictly prohibited.

    xxxxxxxxxx
    <font size="+3"><strong>Visualizing Data: Matplotlib</strong></font>
    Advanced Tools
    xxxxxxxxxx
    xxxxxxxxxx

    -

    Variables

    Callstack

      Breakpoints

      Source

      xxxxxxxxxx
      1
      06-visualization-matplotlib.2022-12-23T07-21-48-609Z.ipynb
      • Boxplots
      • Histograms
      • Bar Charts
      • Scatter Plots
      • Add a vertical or horizontal line across a plot
      • Sources & Further Reading
        0
        16
        Python 3 (ipykernel) | Idle
        Saving completed
        Uploading…
        06-visualization-matplotlib.2022-12-23T07-21-48-609Z.ipynb
        English (United States)
        Spaces: 4
        Ln 1, Col 1
        Mode: Command
        • Console
        • Change Kernel…
        • Clear Console Cells
        • Close and Shut Down…
        • Insert Line Break
        • Interrupt Kernel
        • New Console
        • Restart Kernel…
        • Run Cell (forced)
        • Run Cell (unforced)
        • Show All Kernel Activity
        • Debugger
        • Continue
          Continue
          F9
        • Evaluate Code
          Evaluate Code
        • Next
          Next
          F10
        • Step In
          Step In
          F11
        • Step Out
          Step Out
          Shift+F11
        • Terminate
          Terminate
          Shift+F9
        • Extension Manager
        • Enable Extension Manager
        • File Operations
        • Autosave Documents
        • Open from Path…
          Open from path
        • Reload Notebook from Disk
          Reload contents from disk
        • Revert Notebook to Checkpoint
          Revert contents to previous checkpoint
        • Save Notebook
          Save and create checkpoint
          Ctrl+S
        • Save Notebook As…
          Save with new path
          Ctrl+Shift+S
        • Show Active File in File Browser
        • Trust HTML File
        • Help
        • About JupyterLab
        • Jupyter Forum
        • Jupyter Reference
        • JupyterLab FAQ
        • JupyterLab Reference
        • Launch Classic Notebook
        • Licenses
        • Markdown Reference
        • Reset Application State
        • Image Viewer
        • Flip image horizontally
          H
        • Flip image vertically
          V
        • Invert Colors
          I
        • Reset Image
          0
        • Rotate Clockwise
          ]
        • Rotate Counterclockwise
          [
        • Zoom In
          =
        • Zoom Out
          -
        • Kernel Operations
        • Shut Down All Kernels…
        • Launcher
        • New Launcher
        • Main Area
        • Activate Next Tab
          Ctrl+Shift+]
        • Activate Next Tab Bar
          Ctrl+Shift+.
        • Activate Previous Tab
          Ctrl+Shift+[
        • Activate Previous Tab Bar
          Ctrl+Shift+,
        • Activate Previously Used Tab
          Ctrl+Shift+'
        • Close All Other Tabs
        • Close All Tabs
        • Close Tab
          Alt+W
        • Close Tabs to Right
        • Find Next
          Ctrl+G
        • Find Previous
          Ctrl+Shift+G
        • Find…
          Ctrl+F
        • Log Out
          Log out of JupyterLab
        • Presentation Mode
        • Show Header Above Content
        • Show Left Sidebar
          Ctrl+B
        • Show Log Console
        • Show Right Sidebar
        • Show Status Bar
        • Shut Down
          Shut down JupyterLab
        • Simple Interface
          Ctrl+Shift+D
        • Notebook Cell Operations
        • Change to Code Cell Type
          Y
        • Change to Heading 1
          1
        • Change to Heading 2
          2
        • Change to Heading 3
          3
        • Change to Heading 4
          4
        • Change to Heading 5
          5
        • Change to Heading 6
          6
        • Change to Markdown Cell Type
          M
        • Change to Raw Cell Type
          R
        • Clear Outputs
        • Collapse All Code
        • Collapse All Outputs
        • Collapse Selected Code
        • Collapse Selected Outputs
        • Copy Cells
          C
        • Cut Cells
          X
        • Delete Cells
          D, D
        • Disable Scrolling for Outputs
        • Enable Scrolling for Outputs
        • Expand All Code
        • Expand All Outputs
        • Expand Selected Code
        • Expand Selected Outputs
        • Extend Selection Above
          Shift+K
        • Extend Selection Below
          Shift+J
        • Extend Selection to Bottom
          Shift+End
        • Extend Selection to Top
          Shift+Home
        • Insert Cell Above
          A
        • Insert Cell Below
          B
        • Merge Cell Above
          Ctrl+Backspace
        • Merge Cell Below
          Ctrl+Shift+M
        • Merge Selected Cells
          Shift+M
        • Move Cells Down
        • Move Cells Up
        • Paste Cells Above
        • Paste Cells and Replace
        • Paste Cells Below
          V
        • Redo Cell Operation
          Shift+Z
        • Run Selected Cells
          Shift+Enter
        • Run Selected Cells and Don't Advance
          Ctrl+Enter
        • Run Selected Cells and Insert Below
          Alt+Enter
        • Run Selected Text or Current Line in Console
        • Select Cell Above
          K
        • Select Cell Below
          J
        • Split Cell
          Ctrl+Shift+-
        • Undo Cell Operation
          Z
        • Notebook Operations
        • Change Kernel…
        • Clear All Outputs
        • Close and Shut Down
        • Collapse All Cells
        • Deselect All Cells
        • Enter Command Mode
          Ctrl+M
        • Enter Edit Mode
          Enter
        • Expand All Headings
        • Interrupt Kernel
        • New Console for Notebook
        • New Notebook
          Create a new notebook
        • Reconnect To Kernel
        • Render All Markdown Cells
        • Restart Kernel and Clear All Outputs…
        • Restart Kernel and Run All Cells…
        • Restart Kernel and Run up to Selected Cell…
        • Restart Kernel…
        • Run All Above Selected Cell
        • Run All Cells
        • Run Selected Cell and All Below
        • Select All Cells
          Ctrl+A
        • Toggle All Line Numbers
          Shift+L
        • Toggle Collapse Notebook Heading
          T
        • Trust Notebook
        • Settings
        • Advanced Settings Editor
          Ctrl+,
        • Show Contextual Help
        • Show Contextual Help
          Live updating code documentation from the active kernel
          Ctrl+I
        • Spell Checker
        • Choose spellchecker language
        • Toggle spellchecker
        • Terminal
        • Decrease Terminal Font Size
        • Increase Terminal Font Size
        • New Terminal
          Start a new terminal session
        • Refresh Terminal
          Refresh the current terminal session
        • Use Terminal Theme: Dark
          Set the terminal theme
        • Use Terminal Theme: Inherit
          Set the terminal theme
        • Use Terminal Theme: Light
          Set the terminal theme
        • Text Editor
        • Decrease Font Size
        • Increase Font Size
        • Indent with Tab
        • New Markdown File
          Create a new markdown file
        • New Python File
          Create a new Python file
        • New Text File
          Create a new text file
        • Spaces: 1
        • Spaces: 2
        • Spaces: 4
        • Spaces: 8
        • Theme
        • Decrease Code Font Size
        • Decrease Content Font Size
        • Decrease UI Font Size
        • Increase Code Font Size
        • Increase Content Font Size
        • Increase UI Font Size
        • Theme Scrollbars
        • Use Theme: JupyterLab Dark
        • Use Theme: JupyterLab Light